nageyqma·git
HTTPS https://git.nageyqma.icu/nageyqma/blade-addons-next.git SSH git@git.nageyqma.icu:nageyqma/blade-addons-next.git

HTTPS 链接可直接 git clone;推送代码请使用 SSH。

仅展示默认分支 next 最近 100 条提交。

add descriptionnageyqma · 2026-09-28 · e6dbfde
查看变更
README.md | 4 ++++
 1 file changed, 4 insertions(+)
diff --git a/README.md b/README.md
index f739b58..2402511 100644
--- a/README.md
+++ b/README.md
@@ -1,3 +1,7 @@
+# Blade Addons - next
+
+This is a fork of [Blade Addons](https://github.com/BladeMasterGabe/blade-addons), with support of latest minecraft version.
+
 # Blade Addons
 
 A dungeons mod made to port dungeons features to the newer Minecraft versions. Huge thanks to Ohful for letting me port
Migrate to Minecraft 26.3 (Fabric API 0.161.0+26.3, ModMenu 21.0.0, loader 0.19.5)
- Bump minecraft_version 26.2 -> 26.3, mod_version 2.6.0 -> 2.7.0, loom 1.17-SNAPSHOT -> 1.18.2, fabric-loader 0.19.3 -> 0.19.5, fabric-api 0.161.0+26.2 -> 0.161.0+26.3, modmenu 20.0.2 -> 21.0.0 - 26.3 replaced the Blaze3D GPU abstraction with the RenderPearl API: com.mojang.blaze3d.pipeline.* -> com.mojang.renderpearl.api.pipeline.* (RenderPipeline/BlendFunction/ColorTargetState/DepthStencilState/PrimitiveTopology/ CompareOp) and VertexFormat -> com.mojang.renderpearl.api.vertex.VertexFormat. BindGroupLayouts.MATRICES_PROJECTION split into PROJECTION + DYNAMIC_TRANSFORMS; the custom FILLED/LINE snippets were rebuilt to match vanilla's 26.3 DEBUG_FILLED_SNIPPET/LINES_SNIPPET composition. PoseStack#mulPose -> rotate and VertexConsumer#setUv3 is now abstract (implemented in RenderHandler's recorder). - GLFW was removed in 26.3 (SDL is used instead): mod and practical-config now use com.mojang.blaze3d.platform.InputConstants (SDL scancodes/keymod) for keybinds, the search bar and chat click handling. - Mixin/method retargets for 26.3: * vanilla renames: EnderMan -> Enderman * ClientboundLevelParticlesPacket is now a record (getX() -> x(), getParticle() -> particle(), getXDist() -> xDist(), ...) * InterpolationHandler#position() -> #target().position() * EntityRenderDispatcher#shouldRender gained a partial-tick float * ItemInHandRenderer was split into net.minecraft.client.player.FirstPersonHandsAndItems (held-item state) + net.minecraft.client.renderer.FirstPersonHandsAndItemsRenderer (arm transforms): HeldItemRendererMixin was split to match * LocalPlayer#drop(boolean) moved to MultiPlayerGameMode#dropItem(LocalPlayer, boolean): drop protection + DropAnimation hooking moved into MultiPlayerGameModeMixin * LivingEntity#swing(InteractionHand, boolean) -> swing(InteractionHand, net.minecraft.world.item.component.SwingAnimation, boolean) * ShovelItem/AxeItem/HoeItem were removed; tool transforms are now data driven via the BLOCK_TRANSFORMER component, so ShovelItemMixin was replaced by BlockTransformerMixin (redirects the transform sound in BlockTransformer#transformBlock, still gated on SHOVEL_FLATTEN so axe stripping keeps its swing) * AbstractContainerScreen#slotClicked gained a MouseButtonEvent overload and Minecraft#updateLevelInEngines stays overloaded: descriptors pinned so the injectors stay unambiguous * GuiGraphicsExtractor#tooltip gained a trailing boolean: callback declares it - practical-config is a local patched build for 26.3 (GLFW -> InputConstants, Minecraft#setScreen/screen -> Gui), served from the project-local maven repo (libs-local) and versioned through the new practical_config_version property. - Packets: no outbound packet format changes. The mod still only issues vanilla ClientPacketListener#sendCommand plus the bundled (Minecraft-independent) net.hypixel:mod-api protocol library; ConnectionMixin remains observation-only. - Verified: ./gradlew clean build green; client boots on 26.3 with all 29 mixins applied (0 failures), enters a world and renders (Vulkan) with no mod-side errors; config screen opens and closes cleanly.
nageyqma · 2026-09-27 · 05c468a
查看变更
README.md                                                                                                   |   2 +-
 build.gradle                                                                                                |   2 +-
 gradle.properties                                                                                           |  13 +++++++------
 libs-local/maven/com/github/BladeMasterGabe/practical-config/26.3-patched/practical-config-26.3-patched.jar | Bin 0 -> 108521 bytes
 libs-local/maven/com/github/BladeMasterGabe/practical-config/26.3-patched/practical-config-26.3-patched.pom |   8 ++++++++
 src/main/java/blade/addon/features/dungeon/f7/dragons/DragonSpawn.java                                      |  19 ++++++++++---------
 src/main/java/blade/addon/features/highlight/MobHighlight.java                                              |   4 ++--
 src/main/java/blade/addon/features/other/SearchBar.java                                                     |  10 +++++-----
 src/main/java/blade/addon/mixin/AbstractContainerScreenMixin.java                                           |   6 ++++--
 src/main/java/blade/addon/mixin/BlockTransformerMixin.java                                                  |  39 +++++++++++++++++++++++++++++++++++++++
 src/main/java/blade/addon/mixin/ChatScreenMixin.java                                                        |   4 ++--
 src/main/java/blade/addon/mixin/EntityRenderDispatcherMixin.java                                            |   6 ++++--
 src/main/java/blade/addon/mixin/{HeldItemRendererMixin.java => FirstPersonHandsAndItemsMixin.java}          |  36 ++++++++++--------------------------
 src/main/java/blade/addon/mixin/FirstPersonHandsAndItemsRendererMixin.java                                  |  37 +++++++++++++++++++++++++++++++++++++
 src/main/java/blade/addon/mixin/GuiGraphicsExtractorMixin.java                                              |   4 +++-
 src/main/java/blade/addon/mixin/LivingEntityMixin.java                                                      |   8 +++++---
 src/main/java/blade/addon/mixin/LocalPlayerMixin.java                                                       |  36 ------------------------------------
 src/main/java/blade/addon/mixin/MinecraftMixin.java                                                         |   3 ++-
 src/main/java/blade/addon/mixin/MultiPlayerGameModeMixin.java                                               |  25 +++++++++++++++++++++++++
 src/main/java/blade/addon/mixin/ShovelItemMixin.java                                                        |  23 -----------------------
 src/main/java/blade/addon/utils/Keybinds.java                                                               |  25 ++++++++++++-------------
 src/main/java/blade/addon/utils/rendering/RenderHandler.java                                                |   7 +++++++
 src/main/java/blade/addon/utils/rendering/RenderPipelines.java                                              |  21 ++++++++++++---------
 src/main/java/blade/addon/utils/rendering/RenderUtils.java                                                  |   2 +-
 src/main/resources/blade-addons.mixins.json                                                                 |   6 +++---
 src/main/resources/fabric.mod.json                                                                          |   4 ++--
 26 files changed, 202 insertions(+), 148 deletions(-)
diff --git a/README.md b/README.md
index b5a055e..f739b58 100644
--- a/README.md
+++ b/README.md
@@ -4,7 +4,7 @@ A dungeons mod made to port dungeons features to the newer Minecraft versions. H
 his amazing mods like Valley addons.
 
 # Current Version
-26.2
+26.3
 
 # Dependencies
 - Hypixel Mod API https://modrinth.com/mod/hypixel-mod-api
diff --git a/build.gradle b/build.gradle
index 9f50376..b74e745 100644
--- a/build.gradle
+++ b/build.gradle
@@ -48,7 +48,7 @@ dependencies {
     implementation "net.fabricmc:fabric-loader:${project.loader_version}"
     implementation "net.fabricmc.fabric-api:fabric-api:${project.fabric_api_version}"
     include implementation("net.hypixel:mod-api:${project.hypixel_api_version}")
-    include implementation("com.github.BladeMasterGabe:practical-config:26.2-patched")
+    include implementation("com.github.BladeMasterGabe:practical-config:${project.practical_config_version}")
 
     runtimeOnly("me.djtheredstoner:DevAuth-fabric:${project.dev_auth_version}")
     compileOnly("com.terraformersmc:modmenu:${project.modmenu_version}")
diff --git a/gradle.properties b/gradle.properties
index 215888e..4dbab9f 100644
--- a/gradle.properties
+++ b/gradle.properties
@@ -5,17 +5,18 @@ org.gradle.parallel=true
 org.gradle.configuration-cache=false
 # Fabric Properties
 # check these on https://fabricmc.net/develop
-minecraft_version=26.2
-loader_version=0.19.3
-loom_version=1.17-SNAPSHOT
+minecraft_version=26.3
+loader_version=0.19.5
+loom_version=1.18.2
 # Mod Properties
-mod_version=2.6.0
+mod_version=2.7.0
 maven_group=blade.addon
 archives_base_name=blade-addons
 # Dependencies
-fabric_api_version=0.161.0+26.2
+fabric_api_version=0.161.0+26.3
 # I love dependencies sometimes breaking
 practical_config=757ad46ab1b257d494cc150261736b6f9b0e131c
-modmenu_version=20.0.2
+practical_config_version=26.3-patched
+modmenu_version=21.0.0
 hypixel_api_version=1.0.2
 dev_auth_version=1.2.2
diff --git a/libs-local/maven/com/github/BladeMasterGabe/practical-config/26.3-patched/practical-config-26.3-patched.jar b/libs-local/maven/com/github/BladeMasterGabe/practical-config/26.3-patched/practical-config-26.3-patched.jar
new file mode 100644
index 0000000..0f58f45
Binary files /dev/null and b/libs-local/maven/com/github/BladeMasterGabe/practical-config/26.3-patched/practical-config-26.3-patched.jar differ
diff --git a/libs-local/maven/com/github/BladeMasterGabe/practical-config/26.3-patched/practical-config-26.3-patched.pom b/libs-local/maven/com/github/BladeMasterGabe/practical-config/26.3-patched/practical-config-26.3-patched.pom
new file mode 100644
index 0000000..7f6dfe6
--- /dev/null
+++ b/libs-local/maven/com/github/BladeMasterGabe/practical-config/26.3-patched/practical-config-26.3-patched.pom
@@ -0,0 +1,8 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0">
+  <modelVersion>4.0.0</modelVersion>
+  <groupId>com.github.BladeMasterGabe</groupId>
+  <artifactId>practical-config</artifactId>
+  <version>26.3-patched</version>
+  <packaging>jar</packaging>
+</project>
diff --git a/src/main/java/blade/addon/features/dungeon/f7/dragons/DragonSpawn.java b/src/main/java/blade/addon/features/dungeon/f7/dragons/DragonSpawn.java
index 45ecb61..2c2402f 100644
--- a/src/main/java/blade/addon/features/dungeon/f7/dragons/DragonSpawn.java
+++ b/src/main/java/blade/addon/features/dungeon/f7/dragons/DragonSpawn.java
@@ -35,7 +35,7 @@ public class DragonSpawn {
     public static void init() {
         Events.ON_PARTICLE.register(packet -> {
             if (!validParticle(packet)) return false;
-            Dragon dragon = Dragon.getDragon(packet.getX(), packet.getY(), packet.getZ());
+            Dragon dragon = Dragon.getDragon(packet.x(), packet.y(), packet.z());
             testDragon(dragon);
             return false;
         });
@@ -75,14 +75,15 @@ public class DragonSpawn {
     }
 
     private static boolean validParticle(ClientboundLevelParticlesPacket packet) {
-        if (packet.getCount() != 20) return false;
-        if (packet.getY() != 19) return false;
-        if (packet.getParticle().getType() != ParticleTypes.FLAME) return false;
-        if (packet.getXDist() != 2) return false;
-        if (packet.getYDist() != 3) return false;
-        if (packet.getZDist() != 2) return false;
-        if (packet.getX() % 1 != 0) return false;
-        if (packet.getZ() % 1 != 0) return false;
+        // 26.3 turned ClientboundLevelParticlesPacket into a record: getters are now record accessors.
+        if (packet.count() != 20) return false;
+        if (packet.y() != 19) return false;
+        if (packet.particle().getType() != ParticleTypes.FLAME) return false;
+        if (packet.xDist() != 2) return false;
+        if (packet.yDist() != 3) return false;
+        if (packet.zDist() != 2) return false;
+        if (packet.x() % 1 != 0) return false;
+        if (packet.z() % 1 != 0) return false;
         return true;
     }
 
diff --git a/src/main/java/blade/addon/features/highlight/MobHighlight.java b/src/main/java/blade/addon/features/highlight/MobHighlight.java
index 1ccb934..1c71c52 100644
--- a/src/main/java/blade/addon/features/highlight/MobHighlight.java
+++ b/src/main/java/blade/addon/features/highlight/MobHighlight.java
@@ -17,7 +17,7 @@ import net.minecraft.network.chat.TextColor;
 import net.minecraft.world.effect.MobEffects;
 import net.minecraft.world.entity.Entity;
 import net.minecraft.world.entity.decoration.ArmorStand;
-import net.minecraft.world.entity.monster.EnderMan;
+import net.minecraft.world.entity.monster.Enderman;
 import net.minecraft.world.entity.monster.zombie.Zombie;
 import net.minecraft.world.entity.player.Player;
 import net.minecraft.world.item.Item;
@@ -287,7 +287,7 @@ public class MobHighlight {
         AABB box = EntityUtil.getBox(entity);
 
         //only shows the head
-        if (entity instanceof EnderMan && entity.isInvisible() && MobHighlight.dontShowInvisibleMobs) {
+        if (entity instanceof Enderman && entity.isInvisible() && MobHighlight.dontShowInvisibleMobs) {
             box = box.inflate(0, -1.8, 0).move(0, -1.2, 0);
         }
 
diff --git a/src/main/java/blade/addon/features/other/SearchBar.java b/src/main/java/blade/addon/features/other/SearchBar.java
index 4733f1f..1128f56 100644
--- a/src/main/java/blade/addon/features/other/SearchBar.java
+++ b/src/main/java/blade/addon/features/other/SearchBar.java
@@ -5,6 +5,7 @@ import blade.addon.utils.config.values.ExtraOptions;
 import blade.addon.utils.data.ItemUtil;
 import blade.addon.utils.rendering.DrawEvents;
 import blade.addon.utils.rendering.RenderUtils;
+import com.mojang.blaze3d.platform.InputConstants;
 import com.mojang.blaze3d.platform.Window;
 import net.minecraft.client.Minecraft;
 import net.minecraft.client.gui.Font;
@@ -15,7 +16,6 @@ import net.minecraft.client.input.KeyEvent;
 import net.minecraft.client.input.MouseButtonEvent;
 import net.minecraft.network.chat.Component;
 import net.minecraft.world.item.ItemStack;
-import org.lwjgl.glfw.GLFW;
 
 import java.util.concurrent.ConcurrentHashMap;
 import java.util.concurrent.CopyOnWriteArrayList;
@@ -79,17 +79,17 @@ public class SearchBar {
     public static boolean keyPressed(KeyEvent input) {
         if (!exists() || !ExtraOptions.toggleableSearchBar) return false;
 
-        boolean ctrlIsPressed = (input.modifiers() & GLFW.GLFW_MOD_CONTROL) != 0;
+        boolean ctrlIsPressed = (input.modifiers() & InputConstants.MOD_CONTROL) != 0;
 
-        if (ctrlIsPressed && input.key() == GLFW.GLFW_KEY_F) {
+        if (ctrlIsPressed && input.key() == InputConstants.KEY_F) {
             shouldDisplay = !shouldDisplay;
             return true;
         } else if (shouldDisplay() && searchBar.isFocused()) {
-            if (input.key() == GLFW.GLFW_KEY_ENTER) {
+            if (input.key() == InputConstants.KEY_RETURN) {
                 if (!Double.isNaN(parsedValue)) {
                     searchBar.setValue(RenderUtils.formatNumber((float) parsedValue));
                 }
-            } else if (input.key() != GLFW.GLFW_KEY_ESCAPE) {
+            } else if (input.key() != InputConstants.KEY_ESCAPE) {
                 searchBar.keyPressed(input);
                 return true;
             }
diff --git a/src/main/java/blade/addon/mixin/AbstractContainerScreenMixin.java b/src/main/java/blade/addon/mixin/AbstractContainerScreenMixin.java
index ce5db12..feac339 100644
--- a/src/main/java/blade/addon/mixin/AbstractContainerScreenMixin.java
+++ b/src/main/java/blade/addon/mixin/AbstractContainerScreenMixin.java
@@ -68,7 +68,9 @@ public abstract class AbstractContainerScreenMixin<T extends AbstractContainerMe
         SearchBar.onMouseClick(click);
     }
 
-    @Inject(method = "slotClicked", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/gui/screens/inventory/AbstractContainerScreen;onMouseClickAction(Lnet/minecraft/world/inventory/Slot;Lnet/minecraft/world/inventory/ContainerInput;)V"), cancellable = true)
+    // 26.3 added a private slotClicked(Slot, int, MouseButtonEvent, ContainerInput) overload, so the
+    // descriptor of the vanilla protected entry point has to be spelled out to stay unambiguous.
+    @Inject(method = "slotClicked(Lnet/minecraft/world/inventory/Slot;IILnet/minecraft/world/inventory/ContainerInput;)V", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/gui/screens/inventory/AbstractContainerScreen;onMouseClickAction(Lnet/minecraft/world/inventory/Slot;Lnet/minecraft/world/inventory/ContainerInput;)V"), cancellable = true)
     public void protectItem(Slot slot, int slotId, int buttonNum, ContainerInput containerInput, CallbackInfo ci) {
 
         ItemStack held = menu.getCarried();
@@ -93,7 +95,7 @@ public abstract class AbstractContainerScreenMixin<T extends AbstractContainerMe
         }
     }
 
-    @Inject(method = "slotClicked", at = @At("HEAD"), cancellable = true)
+    @Inject(method = "slotClicked(Lnet/minecraft/world/inventory/Slot;IILnet/minecraft/world/inventory/ContainerInput;)V", at = @At("HEAD"), cancellable = true)
     public void slotClicked(Slot slot, int slotId, int buttonNum, ContainerInput containerInput, CallbackInfo ci) {
         if (Events.ON_SLOT_CLICKED.invoke(slotEvent -> slotEvent.onSlot(slot, slotId, buttonNum, containerInput, this))) {
             ci.cancel();
diff --git a/src/main/java/blade/addon/mixin/BlockTransformerMixin.java b/src/main/java/blade/addon/mixin/BlockTransformerMixin.java
new file mode 100644
index 0000000..e0187b9
--- /dev/null
+++ b/src/main/java/blade/addon/mixin/BlockTransformerMixin.java
@@ -0,0 +1,39 @@
+package blade.addon.mixin;
+
+import blade.addon.features.other.SwingAnimation;
+import blade.addon.utils.config.values.Visual;
+import blade.addon.utils.data.EntityUtil;
+import net.minecraft.core.BlockPos;
+import net.minecraft.core.component.BlockTransformer;
+import net.minecraft.sounds.SoundEvent;
+import net.minecraft.sounds.SoundEvents;
+import net.minecraft.sounds.SoundSource;
+import net.minecraft.world.entity.Entity;
+import net.minecraft.world.level.Level;
+import org.spongepowered.asm.mixin.Mixin;
+import org.spongepowered.asm.mixin.injection.At;
+import org.spongepowered.asm.mixin.injection.Redirect;
+
+/**
+ * 26.3 removed the dedicated tool item classes (ShovelItem / AxeItem / HoeItem). Their
+ * right-click behaviour is now data driven through the BLOCK_TRANSFORMER item component,
+ * applied from Item#useOn via net.minecraft.core.component.BlockTransformer#transformBlock,
+ * which is also where the transform sound is played. This replaces the old ShovelItemMixin,
+ * which hooked ShovelItem#useOn at the Level#playSound call.
+ */
+@Mixin(BlockTransformer.class)
+public class BlockTransformerMixin {
+
+    @Redirect(method = "transformBlock", at = @At(value = "INVOKE", target = "Lnet/minecraft/world/level/Level;playSound(Lnet/minecraft/world/entity/Entity;Lnet/minecraft/core/BlockPos;Lnet/minecraft/sounds/SoundEvent;Lnet/minecraft/sounds/SoundSource;FF)V"))
+    private void bladeAddons$onTransformSound(Level level, Entity entity, BlockPos pos, SoundEvent sound, SoundSource source, float volume, float pitch) {
+        // Only the shovel flatten sound, so that e.g. axe stripping keeps its normal swing.
+        if (Visual.stopShovelFlattening
+                && sound != null
+                && sound.location().equals(SoundEvents.SHOVEL_FLATTEN.value().location())
+                && EntityUtil.isClientPlayer(entity)) {
+            SwingAnimation.ignoreNext(2);
+        }
+
+        level.playSound(entity, pos, sound, source, volume, pitch);
+    }
+}
\ No newline at end of file
diff --git a/src/main/java/blade/addon/mixin/ChatScreenMixin.java b/src/main/java/blade/addon/mixin/ChatScreenMixin.java
index da8eeeb..22e043d 100644
--- a/src/main/java/blade/addon/mixin/ChatScreenMixin.java
+++ b/src/main/java/blade/addon/mixin/ChatScreenMixin.java
@@ -3,7 +3,7 @@ package blade.addon.mixin;
 import blade.addon.utils.Misc;
 import blade.addon.utils.config.values.ExtraOptions;
 import blade.addon.utils.data.TextUtil;
-import org.lwjgl.glfw.GLFW;
+import com.mojang.blaze3d.platform.InputConstants;
 import org.spongepowered.asm.mixin.Mixin;
 import org.spongepowered.asm.mixin.Unique;
 import org.spongepowered.asm.mixin.injection.At;
@@ -25,7 +25,7 @@ public class ChatScreenMixin {
     @Inject(method = "mouseClicked", at = @At("HEAD"))
     private void click(MouseButtonEvent click, boolean doubled, CallbackInfoReturnable<Boolean> cir) {
 
-        if (!ExtraOptions.copyChat || click.button() != GLFW.GLFW_MOUSE_BUTTON_RIGHT) return;
+        if (!ExtraOptions.copyChat || click.button() != InputConstants.MOUSE_BUTTON_RIGHT) return;
 
         Minecraft mc = Minecraft.getInstance();
         ChatComponent chatHud = mc.gui.hud.getChat();
diff --git a/src/main/java/blade/addon/mixin/EntityRenderDispatcherMixin.java b/src/main/java/blade/addon/mixin/EntityRenderDispatcherMixin.java
index 2b04349..c441306 100644
--- a/src/main/java/blade/addon/mixin/EntityRenderDispatcherMixin.java
+++ b/src/main/java/blade/addon/mixin/EntityRenderDispatcherMixin.java
@@ -25,8 +25,9 @@ import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
 @Mixin(EntityRenderDispatcher.class)
 public class EntityRenderDispatcherMixin {
 
+    // 26.3 added a partial-tick float parameter to shouldRender.
     @Inject(method = "shouldRender", at = @At("TAIL"), cancellable = true)
-    private <E extends Entity> void shouldRender(E entity, Frustum frustum, double x, double y, double z, CallbackInfoReturnable<Boolean> cir) {
+    private <E extends Entity> void shouldRender(E entity, Frustum frustum, double x, double y, double z, float partialTick, CallbackInfoReturnable<Boolean> cir) {
         if (entity instanceof Player player) {
             LocalPlayer clientPlayer = Minecraft.getInstance().player;
             if (clientPlayer != null) {
@@ -59,7 +60,8 @@ public class EntityRenderDispatcherMixin {
 
             //Pretty much just hides the bobber if it's too close
             //its def not exact, but it's good enough for now
-            Vec3 pos = bobber.getInterpolation().position();
+            // 26.3 moved the interpolated position from InterpolationHandler#position() to #target().position().
+            Vec3 pos = bobber.getInterpolation().target().position();
             if (clientPlayer.position().distanceTo(pos) < 2 && bobber.tickCount < 6 && pos.y > clientPlayer.getY() + 1.2 && clientPlayer.getXRot() > -60) {
                 cir.setReturnValue(false);
             }
diff --git a/src/main/java/blade/addon/mixin/HeldItemRendererMixin.java b/src/main/java/blade/addon/mixin/FirstPersonHandsAndItemsMixin.java
similarity index 60%
rename from src/main/java/blade/addon/mixin/HeldItemRendererMixin.java
rename to src/main/java/blade/addon/mixin/FirstPersonHandsAndItemsMixin.java
index 5de176f..f349342 100644
--- a/src/main/java/blade/addon/mixin/HeldItemRendererMixin.java
+++ b/src/main/java/blade/addon/mixin/FirstPersonHandsAndItemsMixin.java
@@ -1,13 +1,9 @@
 package blade.addon.mixin;
 
 import blade.addon.features.item.DropAnimation;
-import blade.addon.features.other.SwingAnimation;
 import com.llamalad7.mixinextras.sugar.Local;
-import com.mojang.blaze3d.vertex.PoseStack;
+import net.minecraft.client.player.FirstPersonHandsAndItems;
 import net.minecraft.client.player.LocalPlayer;
-import net.minecraft.client.renderer.ItemInHandRenderer;
-import net.minecraft.util.Mth;
-import net.minecraft.world.entity.HumanoidArm;
 import net.minecraft.world.item.ItemStack;
 import net.minecraft.world.item.Items;
 import org.spongepowered.asm.mixin.Mixin;
@@ -16,8 +12,14 @@ import org.spongepowered.asm.mixin.injection.At;
 import org.spongepowered.asm.mixin.injection.Inject;
 import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
 
-@Mixin(ItemInHandRenderer.class)
-public abstract class HeldItemRendererMixin {
+/**
+ * 26.3 split the old net.minecraft.client.renderer.ItemInHandRenderer: the held item state
+ * (mainHandItem / mainHandHeight and the per-tick update) now lives in
+ * net.minecraft.client.player.FirstPersonHandsAndItems, while the actual rendering moved to
+ * net.minecraft.client.renderer.FirstPersonHandsAndItemsRenderer.
+ */
+@Mixin(FirstPersonHandsAndItems.class)
+public abstract class FirstPersonHandsAndItemsMixin {
 
     @Shadow
     private ItemStack mainHandItem;
@@ -53,22 +55,4 @@ public abstract class HeldItemRendererMixin {
         mainHandItem = stack;
         this.mainHandHeight = 1;
     }
-
-    @Inject(method = "applyItemArmTransform", at = @At("HEAD"), cancellable = true)
-    private void applyEquipOffset(PoseStack matrices, HumanoidArm arm, float swingProgress, CallbackInfo ci) {
-        if (!SwingAnimation.shouldIgnore()) return;
-        int side = arm == HumanoidArm.RIGHT ? 1 : -1;
-        matrices.translate((float) side * 0.56F, -0.52F, -0.72F);
-        ci.cancel();
-    }
-
-    @Inject(method = "swingArm", at = @At("HEAD"), cancellable = true)
-    private void swingArm(float swingProgress, PoseStack matrixStack, int i, HumanoidArm arm, CallbackInfo ci) {
-        if (!SwingAnimation.shouldIgnore()) return;
-        float f = -0.4F * Mth.sin(Mth.sqrt(1) * (float) Math.PI);
-        float g = 0.2F * Mth.sin(Mth.sqrt(1) * (float) (Math.PI * 2));
-        float h = -0.2F * Mth.sin(1 * (float) Math.PI);
-        matrixStack.translate(i * f, g, h);
-        ci.cancel();
-    }
-}
+}
\ No newline at end of file
diff --git a/src/main/java/blade/addon/mixin/FirstPersonHandsAndItemsRendererMixin.java b/src/main/java/blade/addon/mixin/FirstPersonHandsAndItemsRendererMixin.java
new file mode 100644
index 0000000..c07907a
--- /dev/null
+++ b/src/main/java/blade/addon/mixin/FirstPersonHandsAndItemsRendererMixin.java
@@ -0,0 +1,37 @@
+package blade.addon.mixin;
+
+import blade.addon.features.other.SwingAnimation;
+import com.mojang.blaze3d.vertex.PoseStack;
+import net.minecraft.client.renderer.FirstPersonHandsAndItemsRenderer;
+import net.minecraft.util.Mth;
+import net.minecraft.world.entity.HumanoidArm;
+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;
+
+/**
+ * 26.3 split the old net.minecraft.client.renderer.ItemInHandRenderer: the arm/item transforms
+ * now live in net.minecraft.client.renderer.FirstPersonHandsAndItemsRenderer.
+ */
+@Mixin(FirstPersonHandsAndItemsRenderer.class)
+public class FirstPersonHandsAndItemsRendererMixin {
+
+    @Inject(method = "applyItemArmTransform", at = @At("HEAD"), cancellable = true)
+    private void applyEquipOffset(PoseStack matrices, HumanoidArm arm, float swingProgress, CallbackInfo ci) {
+        if (!SwingAnimation.shouldIgnore()) return;
+        int side = arm == HumanoidArm.RIGHT ? 1 : -1;
+        matrices.translate((float) side * 0.56F, -0.52F, -0.72F);
+        ci.cancel();
+    }
+
+    @Inject(method = "swingArm", at = @At("HEAD"), cancellable = true)
+    private void swingArm(float swingProgress, PoseStack matrixStack, int i, HumanoidArm arm, CallbackInfo ci) {
+        if (!SwingAnimation.shouldIgnore()) return;
+        float f = -0.4F * Mth.sin(Mth.sqrt(1) * (float) Math.PI);
+        float g = 0.2F * Mth.sin(Mth.sqrt(1) * (float) (Math.PI * 2));
+        float h = -0.2F * Mth.sin(1 * (float) Math.PI);
+        matrixStack.translate(i * f, g, h);
+        ci.cancel();
+    }
+}
\ No newline at end of file
diff --git a/src/main/java/blade/addon/mixin/GuiGraphicsExtractorMixin.java b/src/main/java/blade/addon/mixin/GuiGraphicsExtractorMixin.java
index 1a6bda3..590ff93 100644
--- a/src/main/java/blade/addon/mixin/GuiGraphicsExtractorMixin.java
+++ b/src/main/java/blade/addon/mixin/GuiGraphicsExtractorMixin.java
@@ -69,9 +69,11 @@ public abstract class GuiGraphicsExtractorMixin {
         return 0;
     }
 
+    // 26.3 appended a trailing boolean to GuiGraphicsExtractor#tooltip, so the callback has to
+    // declare it (Mixin requires either all of the target arguments or none of them).
     @Inject(method = "tooltip", at = @At(value = "INVOKE", target = "Lorg/joml/Matrix3x2fStack;pushMatrix()Lorg/joml/Matrix3x2fStack;"))
     private void scaleUpTooltip_head(Font font, List<ClientTooltipComponent> lines, int xo, int yo, ClientTooltipPositioner positioner,
-                                     @Nullable Identifier style, CallbackInfo ci, @Local(name = "h") int h) {
+                                     @Nullable Identifier style, boolean bl, CallbackInfo ci, @Local(name = "h") int h) {
         if (Visual.tooltipSize == 1) return;
         float newX = xo + 6;
         float newY = yo - ((float) h / 2) * Visual.tooltipSize + 4;
diff --git a/src/main/java/blade/addon/mixin/LivingEntityMixin.java b/src/main/java/blade/addon/mixin/LivingEntityMixin.java
index a82cdcd..43132c3 100644
--- a/src/main/java/blade/addon/mixin/LivingEntityMixin.java
+++ b/src/main/java/blade/addon/mixin/LivingEntityMixin.java
@@ -8,14 +8,16 @@ import net.minecraft.world.entity.LivingEntity;
 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 org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
 
 @Mixin(LivingEntity.class)
 public class LivingEntityMixin {
 
 
-    @Inject(method = "swing(Lnet/minecraft/world/InteractionHand;Z)V", at = @At("TAIL"))
-    public void swingHand(InteractionHand hand, boolean fromServerPlayer, CallbackInfo ci) {
+    // 26.3 replaced swing(InteractionHand, boolean) with
+    // swing(InteractionHand, net.minecraft.world.item.component.SwingAnimation, boolean).
+    @Inject(method = "swing(Lnet/minecraft/world/InteractionHand;Lnet/minecraft/world/item/component/SwingAnimation;Z)Z", at = @At("TAIL"))
+    public void swingHand(InteractionHand hand, net.minecraft.world.item.component.SwingAnimation animation, boolean fromServerPlayer, CallbackInfoReturnable<Boolean> cir) {
         if (EntityUtil.isClientPlayer((Entity) (Object) this)) {
             SwingAnimation.consumeSwing();
         }
diff --git a/src/main/java/blade/addon/mixin/LocalPlayerMixin.java b/src/main/java/blade/addon/mixin/LocalPlayerMixin.java
deleted file mode 100644
index a029285..0000000
--- a/src/main/java/blade/addon/mixin/LocalPlayerMixin.java
+++ /dev/null
@@ -1,36 +0,0 @@
-package blade.addon.mixin;
-
-import blade.addon.features.item.DropAnimation;
-import blade.addon.features.item.ProtectItem;
-import blade.addon.utils.config.values.Dungeons;
-import com.llamalad7.mixinextras.sugar.Local;
-import net.minecraft.client.player.LocalPlayer;
-import net.minecraft.world.item.ItemStack;
-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.CallbackInfoReturnable;
-
-@Mixin(LocalPlayer.class)
-public class LocalPlayerMixin {
-
-    @Inject(method = "drop(Z)Z", at = @At("HEAD"), cancellable = true)
-    public void protectItem(boolean entireStack, CallbackInfoReturnable<Boolean> cir) {
-        if (Dungeons.dontProtectHeldItem && ProtectItem.isInADungeon()) return;
-
-        LocalPlayer self = (LocalPlayer) (Object) this;
-
-        if (ProtectItem.protect(self.getInventory().getSelectedItem())) {
-            cir.setReturnValue(false);
-        }
-    }
-
-    @Inject(method = "drop(Z)Z", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/multiplayer/ClientPacketListener;send(Lnet/minecraft/network/protocol/Packet;)V"))
-    public void getDropped(boolean entireStack, CallbackInfoReturnable<Boolean> cir, @Local ItemStack itemStack) {
-        if (!DropAnimation.shouldProceed()) return;
-
-        LocalPlayer self = (LocalPlayer) (Object) this;
-        int slot = self.getInventory().getSelectedSlot();
-        DropAnimation.setDroppedData(itemStack, slot);
-    }
-}
diff --git a/src/main/java/blade/addon/mixin/MinecraftMixin.java b/src/main/java/blade/addon/mixin/MinecraftMixin.java
index 78a64f8..49e8fa0 100644
--- a/src/main/java/blade/addon/mixin/MinecraftMixin.java
+++ b/src/main/java/blade/addon/mixin/MinecraftMixin.java
@@ -38,7 +38,8 @@ public class MinecraftMixin {
         DropAnimation.clearData();
     }
 
-    @Inject(method = "updateLevelInEngines", at = @At(value = "TAIL"))
+    // 26.3 keeps two updateLevelInEngines overloads, so spell out the descriptor.
+    @Inject(method = "updateLevelInEngines(Lnet/minecraft/client/multiplayer/ClientLevel;)V", at = @At(value = "TAIL"))
     private void onWorld(ClientLevel world, CallbackInfo ci) {
         Events.ON_WORLD_CHANGE.invoke(WorldEvent::onWorldSwap);
     }
diff --git a/src/main/java/blade/addon/mixin/MultiPlayerGameModeMixin.java b/src/main/java/blade/addon/mixin/MultiPlayerGameModeMixin.java
index 14fe753..7b59d16 100644
--- a/src/main/java/blade/addon/mixin/MultiPlayerGameModeMixin.java
+++ b/src/main/java/blade/addon/mixin/MultiPlayerGameModeMixin.java
@@ -1,9 +1,15 @@
 package blade.addon.mixin;
 
+import blade.addon.features.item.DropAnimation;
+import blade.addon.features.item.ProtectItem;
 import blade.addon.features.other.pet.SelectedPet;
+import blade.addon.utils.config.values.Dungeons;
+import com.llamalad7.mixinextras.sugar.Local;
 import net.minecraft.client.multiplayer.MultiPlayerGameMode;
+import net.minecraft.client.player.LocalPlayer;
 import net.minecraft.world.entity.player.Player;
 import net.minecraft.world.inventory.ContainerInput;
+import net.minecraft.world.item.ItemStack;
 import org.spongepowered.asm.mixin.Mixin;
 import org.spongepowered.asm.mixin.injection.At;
 import org.spongepowered.asm.mixin.injection.Inject;
@@ -17,4 +23,23 @@ public class MultiPlayerGameModeMixin {
         SelectedPet.testLoadoutClick(container, slotId, button, containerInput, player);
     }
 
+    // 26.3 moved the drop handling from LocalPlayer#drop(boolean) to
+    // MultiPlayerGameMode#dropItem(LocalPlayer, boolean) (see LocalPlayerMixin for 26.2).
+    @Inject(method = "dropItem", at = @At("HEAD"), cancellable = true)
+    public void protectItem(LocalPlayer player, boolean entireStack, CallbackInfo ci) {
+        if (Dungeons.dontProtectHeldItem && ProtectItem.isInADungeon()) return;
+
+        if (ProtectItem.protect(player.getInventory().getSelectedItem())) {
+            ci.cancel();
+        }
+    }
+
+    @Inject(method = "dropItem", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/multiplayer/ClientPacketListener;send(Lnet/minecraft/network/protocol/Packet;)V"))
+    public void getDropped(LocalPlayer player, boolean entireStack, CallbackInfo ci, @Local ItemStack itemStack) {
+        if (!DropAnimation.shouldProceed()) return;
+
+        int slot = player.getInventory().getSelectedSlot();
+        DropAnimation.setDroppedData(itemStack, slot);
+    }
+
 }
diff --git a/src/main/java/blade/addon/mixin/ShovelItemMixin.java b/src/main/java/blade/addon/mixin/ShovelItemMixin.java
deleted file mode 100644
index 9944873..0000000
--- a/src/main/java/blade/addon/mixin/ShovelItemMixin.java
+++ /dev/null
@@ -1,23 +0,0 @@
-package blade.addon.mixin;
-
-import blade.addon.features.other.SwingAnimation;
-import blade.addon.utils.config.values.Visual;
-import blade.addon.utils.data.EntityUtil;
-import net.minecraft.world.InteractionResult;
-import net.minecraft.world.item.ShovelItem;
-import net.minecraft.world.item.context.UseOnContext;
-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.CallbackInfoReturnable;
-
-@Mixin(ShovelItem.class)
-public class ShovelItemMixin {
-
-    @Inject(method = "useOn", at= @At(value = "INVOKE", target = "Lnet/minecraft/world/level/Level;playSound(Lnet/minecraft/world/entity/Entity;Lnet/minecraft/core/BlockPos;Lnet/minecraft/sounds/SoundEvent;Lnet/minecraft/sounds/SoundSource;FF)V"))
-    public void useOnBlock(UseOnContext context, CallbackInfoReturnable<InteractionResult> cir) {
-        if (Visual.stopShovelFlattening && EntityUtil.isClientPlayer(context.getPlayer())) {
-            SwingAnimation.ignoreNext(2);
-        }
-    }
-}
diff --git a/src/main/java/blade/addon/utils/Keybinds.java b/src/main/java/blade/addon/utils/Keybinds.java
index 43d75b3..c6fca0b 100644
--- a/src/main/java/blade/addon/utils/Keybinds.java
+++ b/src/main/java/blade/addon/utils/Keybinds.java
@@ -23,7 +23,6 @@ import net.minecraft.world.level.block.entity.SkullBlockEntity;
 import net.minecraft.world.level.block.state.BlockState;
 import net.minecraft.world.phys.BlockHitResult;
 import net.minecraft.world.phys.HitResult;
-import org.lwjgl.glfw.GLFW;
 
 import java.util.List;
 
@@ -44,39 +43,39 @@ public class Keybinds {
         //normal keybinds
         openConfig = KeyMappingHelper.registerKeyMapping(new KeyMapping(
                 "opens Config",
-                InputConstants.Type.KEYSYM,
-                GLFW.GLFW_KEY_RIGHT_SHIFT,
+                InputConstants.Type.KEYBOARD,
+                InputConstants.KEY_RSHIFT,
                 category));
 
         trades = KeyMappingHelper.registerKeyMapping(new KeyMapping(
                 "Opens the trades menu",
-                InputConstants.Type.KEYSYM,
-                GLFW.GLFW_KEY_UNKNOWN,
+                InputConstants.Type.KEYBOARD,
+                InputConstants.UNKNOWN.getValue(),
                 category));
 
         potions = KeyMappingHelper.registerKeyMapping(new KeyMapping(
                 "Opens the potion bag",
-                InputConstants.Type.KEYSYM,
-                GLFW.GLFW_KEY_UNKNOWN,
+                InputConstants.Type.KEYBOARD,
+                InputConstants.UNKNOWN.getValue(),
                 category));
 
         //debug keybinds
         getItemLore = KeyMappingHelper.registerKeyMapping(new KeyMapping(
                 "Grabs the items lore",
-                InputConstants.Type.KEYSYM,
-                GLFW.GLFW_KEY_UNKNOWN,
+                InputConstants.Type.KEYBOARD,
+                InputConstants.UNKNOWN.getValue(),
                 category));
 
         getItemCustomData = KeyMappingHelper.registerKeyMapping(new KeyMapping(
                 "Grabs the items custom data",
-                InputConstants.Type.KEYSYM,
-                GLFW.GLFW_KEY_UNKNOWN,
+                InputConstants.Type.KEYBOARD,
+                InputConstants.UNKNOWN.getValue(),
                 category));
 
         getBlockInfo = KeyMappingHelper.registerKeyMapping(new KeyMapping(
                 "Grabs the blocks data",
-                InputConstants.Type.KEYSYM,
-                GLFW.GLFW_KEY_UNKNOWN,
+                InputConstants.Type.KEYBOARD,
+                InputConstants.UNKNOWN.getValue(),
                 category));
 
         ClientTickEvents.END_CLIENT_TICK.register(Keybinds::checkInputs);
diff --git a/src/main/java/blade/addon/utils/rendering/RenderHandler.java b/src/main/java/blade/addon/utils/rendering/RenderHandler.java
index 53ddff1..28ce2d3 100644
--- a/src/main/java/blade/addon/utils/rendering/RenderHandler.java
+++ b/src/main/java/blade/addon/utils/rendering/RenderHandler.java
@@ -91,6 +91,13 @@ public class RenderHandler {
             return this;
         }
 
+        // New abstract method on VertexConsumer in 26.3 (third UV set for the renderpearl vertex formats).
+        @Override
+        public VertexConsumer setUv3(float u, float v) {
+            ops.add(consumer -> consumer.setUv3(u, v));
+            return this;
+        }
+
         @Override
         public VertexConsumer setNormal(float x, float y, float z) {
             ops.add(consumer -> consumer.setNormal(x, y, z));
diff --git a/src/main/java/blade/addon/utils/rendering/RenderPipelines.java b/src/main/java/blade/addon/utils/rendering/RenderPipelines.java
index 147fca3..da88eff 100644
--- a/src/main/java/blade/addon/utils/rendering/RenderPipelines.java
+++ b/src/main/java/blade/addon/utils/rendering/RenderPipelines.java
@@ -1,26 +1,28 @@
 package blade.addon.utils.rendering;
 
 import blade.addon.utils.Constants;
-import com.mojang.blaze3d.PrimitiveTopology;
-import com.mojang.blaze3d.pipeline.BlendFunction;
-import com.mojang.blaze3d.pipeline.ColorTargetState;
-import com.mojang.blaze3d.pipeline.DepthStencilState;
-import com.mojang.blaze3d.pipeline.RenderPipeline;
-import com.mojang.blaze3d.platform.CompareOp;
 import com.mojang.blaze3d.vertex.DefaultVertexFormat;
+import com.mojang.renderpearl.api.pipeline.BlendFunction;
+import com.mojang.renderpearl.api.pipeline.ColorTargetState;
+import com.mojang.renderpearl.api.pipeline.CompareOp;
+import com.mojang.renderpearl.api.pipeline.DepthStencilState;
+import com.mojang.renderpearl.api.pipeline.PrimitiveTopology;
+import com.mojang.renderpearl.api.pipeline.RenderPipeline;
 import net.minecraft.client.renderer.BindGroupLayouts;
 import net.minecraft.resources.Identifier;
 
 public class RenderPipelines {
 
-    // Vanilla's DEBUG_FILLED_SNIPPET / LINES_SNIPPET are private in 26.2; rebuilt here with identical
+    // Vanilla's DEBUG_FILLED_SNIPPET / LINES_SNIPPET are private in 26.3; rebuilt here with identical
     // composition (see net.minecraft.client.renderer.RenderPipelines static initializer).
+    // 26.3 moved the whole pipeline API from com.mojang.blaze3d.pipeline to com.mojang.renderpearl.api.pipeline.
     private static final RenderPipeline.Snippet GLOBALS_SNIPPET = RenderPipeline.builder()
             .withBindGroupLayout(BindGroupLayouts.GLOBALS)
             .buildSnippet();
 
     private static final RenderPipeline.Snippet DEBUG_FILLED_SNIPPET = RenderPipeline.builder(GLOBALS_SNIPPET)
-            .withBindGroupLayout(BindGroupLayouts.MATRICES_PROJECTION)
+            .withBindGroupLayout(BindGroupLayouts.PROJECTION)
+            .withBindGroupLayout(BindGroupLayouts.DYNAMIC_TRANSFORMS)
             .withVertexShader("core/position_color")
             .withFragmentShader("core/position_color")
             .withColorTargetState(new ColorTargetState(BlendFunction.TRANSLUCENT))
@@ -31,7 +33,8 @@ public class RenderPipelines {
             .buildSnippet();
 
     private static final RenderPipeline.Snippet LINES_SNIPPET = RenderPipeline.builder(GLOBALS_SNIPPET)
-            .withBindGroupLayout(BindGroupLayouts.MATRICES_PROJECTION)
+            .withBindGroupLayout(BindGroupLayouts.PROJECTION)
+            .withBindGroupLayout(BindGroupLayouts.DYNAMIC_TRANSFORMS)
             .withBindGroupLayout(BindGroupLayouts.FOG)
             .withVertexShader("core/rendertype_lines")
             .withFragmentShader("core/rendertype_lines")
diff --git a/src/main/java/blade/addon/utils/rendering/RenderUtils.java b/src/main/java/blade/addon/utils/rendering/RenderUtils.java
index 87f7df3..a834251 100644
--- a/src/main/java/blade/addon/utils/rendering/RenderUtils.java
+++ b/src/main/java/blade/addon/utils/rendering/RenderUtils.java
@@ -98,7 +98,7 @@ public class RenderUtils {
 
         matrices.pushPose();
         matrices.translate(x, y, z);
-        matrices.mulPose(context.levelState().cameraRenderState.orientation);
+        matrices.rotate(context.levelState().cameraRenderState.orientation);
         matrices.scale(TEXT_SCALE * scale, -TEXT_SCALE * scale, TEXT_SCALE * scale);
 
         float halfWidth = textRenderer.width(text.getString()) / 2f;
diff --git a/src/main/resources/blade-addons.mixins.json b/src/main/resources/blade-addons.mixins.json
index cab3807..944ba76 100644
--- a/src/main/resources/blade-addons.mixins.json
+++ b/src/main/resources/blade-addons.mixins.json
@@ -12,7 +12,7 @@
     "ItemStackMixin",
     "LevelChunkMixin",
     "LivingEntityMixin",
-    "ShovelItemMixin"
+    "BlockTransformerMixin"
   ],
   "injectors": {
     "defaultRequire": 1
@@ -29,13 +29,13 @@
     "EffectsInInventoryMixin",
     "EntityRenderDispatcherMixin",
     "EntityRendererMixin",
+    "FirstPersonHandsAndItemsMixin",
+    "FirstPersonHandsAndItemsRendererMixin",
     "GuiGraphicsExtractorMixin",
     "GuiMixin",
-    "HeldItemRendererMixin",
     "HudMixin",
     "InventoryScreenMixin",
     "KeyboardHandlerMixin",
-    "LocalPlayerMixin",
     "MinecraftMixin",
     "MouseHandlerMixin",
     "MultiPlayerGameModeMixin",
diff --git a/src/main/resources/fabric.mod.json b/src/main/resources/fabric.mod.json
index b63b8b0..d6c89cf 100644
--- a/src/main/resources/fabric.mod.json
+++ b/src/main/resources/fabric.mod.json
@@ -26,8 +26,8 @@
 		"blade-addons.mixins.json"
 	],
 	"depends": {
-		"fabricloader": ">=0.19.3",
-		"minecraft": "~26.2",
+		"fabricloader": ">=0.19.5",
+		"minecraft": "~26.3",
 		"java": ">=25",
 		"fabric-api": "*"
 	},
Migrate to Minecraft 26.2 (Fabric API 0.161.0+26.2, ModMenu 20.0.2)
- Bump minecraft_version 26.1.2 -> 26.2, mod_version 2.5.3 -> 2.6.0 - Gui/Hud split: setScreen/screen/getChat/setTitle repathed via gui/gui.hud - Replace removed net.minecraft.util.Tuple with datafixerupper Pair - BlockPos.getCenter -> Vec3.atCenterOf; ChatFormatting accessors via TextColor - Rebuild custom RenderPipelines on 26.2 Blaze3D API (withVertexBinding, withPrimitiveTopology, BindGroupLayouts); explicit snippet rebuilds - Replace removed MultiBufferSource/bufferSource with submitNodeCollector submitCustomGeometry (recording consumer); drawInBatch -> submitText - Register all render handlers on COLLECT_SUBMITS (collection closes before AFTER_TRANSLUCENT_FEATURES in 26.2) - Mixin retargets for 26.2: new HudMixin, GuiMixin setScreen, ArrowLayerMixin AvatarRenderState, EnderPearlItemMixin InteractionResult, ChatScreenMixin - practical-config: local patched build (minecraft ~26.2) served from project-local maven repo (libs-local), nested via include - Drop hypixel-mod-api from depends (bundled jar ships no mod metadata) - Verified: ./gradlew build green; client boots and renders on OpenGL and Vulkan (custom pipeline GLSL->SPIR-V compile clean)
nageyqma · 2026-09-21 · 48988b4
查看变更
README.md                                                                                            |   2 +-
 build.gradle                                                                                         |   7 ++--
 gradle.properties                                                                                    |   8 ++---
 .../maven/com/github/BladeMasterGabe/practical-config/26.2-patched/practical-config-26.2-patched.jar | Bin 0 -> 107039 bytes
 .../maven/com/github/BladeMasterGabe/practical-config/26.2-patched/practical-config-26.2-patched.pom |   8 +++++
 src/main/java/blade/addon/features/filter/FilterEntry.java                                           |  14 ++++----
 src/main/java/blade/addon/features/filter/FilterList.java                                            |  18 +++++-----
 src/main/java/blade/addon/features/notifications/NotificationEditScreen.java                         |   4 +--
 src/main/java/blade/addon/features/notifications/NotificationEntry.java                              |  16 ++++-----
 src/main/java/blade/addon/features/notifications/NotificationList.java                               |   4 +--
 src/main/java/blade/addon/features/other/PracticeSS.java                                             |   3 +-
 src/main/java/blade/addon/mixin/ArrowLayerMixin.java                                                 |   3 +-
 src/main/java/blade/addon/mixin/ChatScreenMixin.java                                                 |   2 +-
 src/main/java/blade/addon/mixin/EnderPearlItemMixin.java                                             |   3 +-
 src/main/java/blade/addon/mixin/GuiMixin.java                                                        |  91 ++++--------------------------------------------
 src/main/java/blade/addon/mixin/HudMixin.java                                                        |  96 +++++++++++++++++++++++++++++++++++++++++++++++++++
 src/main/java/blade/addon/mixin/MinecraftMixin.java                                                  |   6 ----
 src/main/java/blade/addon/utils/Keybinds.java                                                        |   2 +-
 src/main/java/blade/addon/utils/Misc.java                                                            |   4 +--
 src/main/java/blade/addon/utils/Scheduler.java                                                       |   2 +-
 src/main/java/blade/addon/utils/config/Config.java                                                   |   4 +--
 src/main/java/blade/addon/utils/data/TextUtil.java                                                   |   8 ++---
 src/main/java/blade/addon/utils/rendering/RenderHandler.java                                         |  70 ++++++++++++++++++++++++++++++++++---
 src/main/java/blade/addon/utils/rendering/RenderPipelines.java                                       |  70 +++++++++++++++++++++++++++++--------
 src/main/java/blade/addon/utils/rendering/RenderUtils.java                                           |   2 +-
 src/main/java/blade/addon/utils/rendering/RenderingEvents.java                                       |  12 ++++---
 src/main/resources/blade-addons.mixins.json                                                          |   1 +
 src/main/resources/fabric.mod.json                                                                   |   5 ++-
 28 files changed, 296 insertions(+), 169 deletions(-)
diff --git a/README.md b/README.md
index 16464d0..b5a055e 100644
--- a/README.md
+++ b/README.md
@@ -4,7 +4,7 @@ A dungeons mod made to port dungeons features to the newer Minecraft versions. H
 his amazing mods like Valley addons.
 
 # Current Version
-26.1.2
+26.2
 
 # Dependencies
 - Hypixel Mod API https://modrinth.com/mod/hypixel-mod-api
diff --git a/build.gradle b/build.gradle
index 2ef553e..9f50376 100644
--- a/build.gradle
+++ b/build.gradle
@@ -23,9 +23,9 @@ loom {
         setIdeConfigGenerated(true)
         vmArgs("-XX:+IgnoreUnrecognizedVMOptions",
                 "-Dmixin.debug.export=true",
-                "-Ddevauth.enabled=true",
-                "-Ddevauth.account=main",
                 "-XX:+AllowEnhancedClassRedefinition")
+//                "-Ddevauth.enabled=true",
+//                "-Ddevauth.account=main")
     }
 
     runConfigs.named("server") {
@@ -34,6 +34,7 @@ loom {
 }
 
 repositories {
+    maven { url = uri("libs-local/maven") }
     mavenCentral()
     maven { url = "https://pkgs.dev.azure.com/djtheredstoner/DevAuth/_packaging/public/maven/v1" }
     maven { url = 'https://jitpack.io' }
@@ -47,7 +48,7 @@ dependencies {
     implementation "net.fabricmc:fabric-loader:${project.loader_version}"
     implementation "net.fabricmc.fabric-api:fabric-api:${project.fabric_api_version}"
     include implementation("net.hypixel:mod-api:${project.hypixel_api_version}")
-    include implementation("com.github.BladeMasterGabe:practical-config:${project.practical_config}")
+    include implementation("com.github.BladeMasterGabe:practical-config:26.2-patched")
 
     runtimeOnly("me.djtheredstoner:DevAuth-fabric:${project.dev_auth_version}")
     compileOnly("com.terraformersmc:modmenu:${project.modmenu_version}")
diff --git a/gradle.properties b/gradle.properties
index 3e9977b..215888e 100644
--- a/gradle.properties
+++ b/gradle.properties
@@ -5,17 +5,17 @@ org.gradle.parallel=true
 org.gradle.configuration-cache=false
 # Fabric Properties
 # check these on https://fabricmc.net/develop
-minecraft_version=26.1.2
+minecraft_version=26.2
 loader_version=0.19.3
 loom_version=1.17-SNAPSHOT
 # Mod Properties
-mod_version=2.5.3
+mod_version=2.6.0
 maven_group=blade.addon
 archives_base_name=blade-addons
 # Dependencies
-fabric_api_version=0.155.2+26.1.2
+fabric_api_version=0.161.0+26.2
 # I love dependencies sometimes breaking
 practical_config=757ad46ab1b257d494cc150261736b6f9b0e131c
-modmenu_version=18.0.0-beta.1
+modmenu_version=20.0.2
 hypixel_api_version=1.0.2
 dev_auth_version=1.2.2
diff --git a/libs-local/maven/com/github/BladeMasterGabe/practical-config/26.2-patched/practical-config-26.2-patched.jar b/libs-local/maven/com/github/BladeMasterGabe/practical-config/26.2-patched/practical-config-26.2-patched.jar
new file mode 100644
index 0000000..916daa9
Binary files /dev/null and b/libs-local/maven/com/github/BladeMasterGabe/practical-config/26.2-patched/practical-config-26.2-patched.jar differ
diff --git a/libs-local/maven/com/github/BladeMasterGabe/practical-config/26.2-patched/practical-config-26.2-patched.pom b/libs-local/maven/com/github/BladeMasterGabe/practical-config/26.2-patched/practical-config-26.2-patched.pom
new file mode 100644
index 0000000..a770ced
--- /dev/null
+++ b/libs-local/maven/com/github/BladeMasterGabe/practical-config/26.2-patched/practical-config-26.2-patched.pom
@@ -0,0 +1,8 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0">
+  <modelVersion>4.0.0</modelVersion>
+  <groupId>com.github.BladeMasterGabe</groupId>
+  <artifactId>practical-config</artifactId>
+  <version>26.2-patched</version>
+  <packaging>jar</packaging>
+</project>
diff --git a/src/main/java/blade/addon/features/filter/FilterEntry.java b/src/main/java/blade/addon/features/filter/FilterEntry.java
index ad79c8a..0c2326c 100644
--- a/src/main/java/blade/addon/features/filter/FilterEntry.java
+++ b/src/main/java/blade/addon/features/filter/FilterEntry.java
@@ -11,7 +11,7 @@ import net.minecraft.client.renderer.RenderPipelines;
 import net.minecraft.network.chat.Component;
 import net.minecraft.resources.Identifier;
 import net.minecraft.util.Mth;
-import net.minecraft.util.Tuple;
+import com.mojang.datafixers.util.Pair;
 
 import java.util.function.Consumer;
 import java.util.function.Supplier;
@@ -44,8 +44,8 @@ public class FilterEntry extends EditBox {
     @Override
     public void extractWidgetRenderState(GuiGraphicsExtractor graphics, int mouseX, int mouseY, float deltaTicks) {
         DrawHelper.drawBackground(graphics, getX(), super.getY(), width - SPRITE_WIDTH_AREA, height, INPUT_COLOR);
-        Tuple<Integer, Integer> pos = getRemovePos();
-        graphics.blitSprite(RenderPipelines.GUI_TEXTURED, CROSS, pos.getA(), pos.getB(), SPRITE_SIZE, SPRITE_SIZE, 0xffffffff);
+        Pair<Integer, Integer> pos = getRemovePos();
+        graphics.blitSprite(RenderPipelines.GUI_TEXTURED, CROSS, pos.getFirst(), pos.getSecond(), SPRITE_SIZE, SPRITE_SIZE, 0xffffffff);
         super.extractWidgetRenderState(graphics, mouseX, mouseY, deltaTicks);
     }
 
@@ -130,13 +130,13 @@ public class FilterEntry extends EditBox {
         return false;
     }
 
-    private Tuple<Integer, Integer> getRemovePos() {
-        return new Tuple<>(getX() + width - SPRITE_SIZE - 5, getY() + (height - SPRITE_SIZE) / 2);
+    private Pair<Integer, Integer> getRemovePos() {
+        return Pair.of(getX() + width - SPRITE_SIZE - 5, getY() + (height - SPRITE_SIZE) / 2);
     }
 
     private boolean inRemovalBounds(double x, double y) {
-        Tuple<Integer, Integer> pos = getRemovePos();
-        return x >= pos.getA() && x <=  pos.getA() + SPRITE_SIZE && y >= pos.getB() && y <= pos.getB() + SPRITE_SIZE;
+        Pair<Integer, Integer> pos = getRemovePos();
+        return x >= pos.getFirst() && x <=  pos.getFirst() + SPRITE_SIZE && y >= pos.getSecond() && y <= pos.getSecond() + SPRITE_SIZE;
     }
 
     @Override
diff --git a/src/main/java/blade/addon/features/filter/FilterList.java b/src/main/java/blade/addon/features/filter/FilterList.java
index fa0584e..a5ebd97 100644
--- a/src/main/java/blade/addon/features/filter/FilterList.java
+++ b/src/main/java/blade/addon/features/filter/FilterList.java
@@ -2,6 +2,7 @@ package blade.addon.features.filter;
 
 import blade.addon.features.notifications.Notifications;
 import com.mojang.blaze3d.platform.Window;
+import com.mojang.datafixers.util.Pair;
 import config.practical.ConfigScroll;
 import config.practical.utilities.Constants;
 import net.minecraft.client.Minecraft;
@@ -9,7 +10,6 @@ import net.minecraft.client.gui.GuiGraphicsExtractor;
 import net.minecraft.client.gui.components.Button;
 import net.minecraft.client.gui.screens.Screen;
 import net.minecraft.network.chat.Component;
-import net.minecraft.util.Tuple;
 import org.joml.Matrix3x2fStack;
 import org.jspecify.annotations.NonNull;
 
@@ -33,11 +33,11 @@ public class FilterList extends Screen {
         super(Component.literal("Filter"));
 
         Minecraft client = Minecraft.getInstance();
-        parent = client.screen;
+        parent = client.gui.screen();
         Window window = client.getWindow();
 
-        Tuple<Integer, Integer> pos = getButtonPos();
-        addFilter = Button.builder(Component.literal("Add Filter"), this::addFilter).pos(pos.getA(), pos.getB()).width(BUTTON_WIDTH).build();
+        Pair<Integer, Integer> pos = getButtonPos();
+        addFilter = Button.builder(Component.literal("Add Filter"), this::addFilter).pos(pos.getFirst(), pos.getSecond()).width(BUTTON_WIDTH).build();
         scroll = new ConfigScroll(0, BUTTON_HEIGHT + TITLE_Y_OFFSET + 16, window.getGuiScaledWidth(), window.getGuiScaledHeight() - BUTTON_HEIGHT, Constants.WIDGET_WIDTH);
     }
 
@@ -53,8 +53,8 @@ public class FilterList extends Screen {
         graphics.text(this.font, this.title, 0, 0, TITLE_COLOR, true);
         stack.popMatrix();
 
-        Tuple<Integer, Integer> pos = getButtonPos();
-        graphics.textWithWordWrap(  this.font, INFO_TEXT, pos.getA() + BUTTON_WIDTH + 5, pos.getB(), 245, 0xffffffff, true);
+        Pair<Integer, Integer> pos = getButtonPos();
+        graphics.textWithWordWrap(  this.font, INFO_TEXT, pos.getFirst() + BUTTON_WIDTH + 5, pos.getSecond(), 245, 0xffffffff, true);
     }
 
     @Override
@@ -65,10 +65,10 @@ public class FilterList extends Screen {
         updateList();
     }
 
-    private Tuple<Integer, Integer> getButtonPos() {
+    private Pair<Integer, Integer> getButtonPos() {
         Minecraft client = Minecraft.getInstance();
         Window window = client.getWindow();
-        return new Tuple<>((window.getGuiScaledWidth() - Constants.WIDGET_WIDTH) / 2, TITLE_Y_OFFSET + 16);
+        return Pair.of((window.getGuiScaledWidth() - Constants.WIDGET_WIDTH) / 2, TITLE_Y_OFFSET + 16);
     }
 
 
@@ -92,7 +92,7 @@ public class FilterList extends Screen {
     public void onClose() {
         assert this.minecraft != null;
         Notifications.save();
-        this.minecraft.setScreen(this.parent);
+        this.minecraft.gui.setScreen(this.parent);
         Filters.filterManager.save();
     }
 
diff --git a/src/main/java/blade/addon/features/notifications/NotificationEditScreen.java b/src/main/java/blade/addon/features/notifications/NotificationEditScreen.java
index 6bf221c..bb61e43 100644
--- a/src/main/java/blade/addon/features/notifications/NotificationEditScreen.java
+++ b/src/main/java/blade/addon/features/notifications/NotificationEditScreen.java
@@ -22,7 +22,7 @@ public class NotificationEditScreen extends Screen {
         super(Component.empty());
 
         Minecraft client = Minecraft.getInstance();
-        parent = client.screen;
+        parent = client.gui.screen();
         Window window = client.getWindow();
         scroll = new ConfigScroll(0,  30, window.getGuiScaledWidth(), window.getGuiScaledHeight(), Constants.WIDGET_WIDTH);
         this.notification = notification;
@@ -51,6 +51,6 @@ public class NotificationEditScreen extends Screen {
     @Override
     public void onClose() {
         assert this.minecraft != null;
-        this.minecraft.setScreen(this.parent);
+        this.minecraft.gui.setScreen(this.parent);
     }
 }
diff --git a/src/main/java/blade/addon/features/notifications/NotificationEntry.java b/src/main/java/blade/addon/features/notifications/NotificationEntry.java
index 809c9cc..fc12c93 100644
--- a/src/main/java/blade/addon/features/notifications/NotificationEntry.java
+++ b/src/main/java/blade/addon/features/notifications/NotificationEntry.java
@@ -1,5 +1,6 @@
 package blade.addon.features.notifications;
 
+import com.mojang.datafixers.util.Pair;
 import config.practical.utilities.Constants;
 import config.practical.utilities.DrawHelper;
 import net.minecraft.client.Minecraft;
@@ -11,7 +12,6 @@ import net.minecraft.client.input.MouseButtonEvent;
 import net.minecraft.client.renderer.RenderPipelines;
 import net.minecraft.network.chat.Component;
 import net.minecraft.resources.Identifier;
-import net.minecraft.util.Tuple;
 
 class NotificationEntry extends AbstractWidget {
 
@@ -47,8 +47,8 @@ class NotificationEntry extends AbstractWidget {
         drawText(graphics, textRenderer, notification.getMatchString(), x + PADDING, y);
         drawText(graphics, textRenderer, notification.getNotificationString(), x + PADDING * 2 + TEXT_SPACE, y);
 
-        Tuple<Integer, Integer> pos = getRemovePos();
-        graphics.blitSprite(RenderPipelines.GUI_TEXTURED, CROSS, pos.getA(), pos.getB(), SPRITE_SIZE, SPRITE_SIZE, 0xffffffff);
+        Pair<Integer, Integer> pos = getRemovePos();
+        graphics.blitSprite(RenderPipelines.GUI_TEXTURED, CROSS, pos.getFirst(), pos.getSecond(), SPRITE_SIZE, SPRITE_SIZE, 0xffffffff);
     }
 
     private void drawText(GuiGraphicsExtractor context, Font textRenderer, String string, int x, int y) {
@@ -67,13 +67,13 @@ class NotificationEntry extends AbstractWidget {
         context.disableScissor();
     }
 
-    private Tuple<Integer, Integer> getRemovePos() {
-        return new Tuple<>(getX() + width - SPRITE_SIZE - 5, getY() + (height - SPRITE_SIZE) / 2);
+    private Pair<Integer, Integer> getRemovePos() {
+        return Pair.of(getX() + width - SPRITE_SIZE - 5, getY() + (height - SPRITE_SIZE) / 2);
     }
 
     private boolean inRemovalBounds(double x, double y) {
-        Tuple<Integer, Integer> pos = getRemovePos();
-        return x >= pos.getA() && x <=  pos.getA() + SPRITE_SIZE && y >= pos.getB() && y <= pos.getB() + SPRITE_SIZE;
+        Pair<Integer, Integer> pos = getRemovePos();
+        return x >= pos.getFirst() && x <=  pos.getFirst() + SPRITE_SIZE && y >= pos.getSecond() && y <= pos.getSecond() + SPRITE_SIZE;
     }
 
     @Override
@@ -89,7 +89,7 @@ class NotificationEntry extends AbstractWidget {
         //make sure you cant click around the button accidentally
         if (width + getX() - SPRITE_WIDTH_AREA < x) return;
 
-        Minecraft.getInstance().setScreen(new NotificationEditScreen(notification));
+        Minecraft.getInstance().gui.setScreen(new NotificationEditScreen(notification));
     }
 
     @Override
diff --git a/src/main/java/blade/addon/features/notifications/NotificationList.java b/src/main/java/blade/addon/features/notifications/NotificationList.java
index 2f28c7b..b54328f 100644
--- a/src/main/java/blade/addon/features/notifications/NotificationList.java
+++ b/src/main/java/blade/addon/features/notifications/NotificationList.java
@@ -28,7 +28,7 @@ public class NotificationList extends Screen {
         super(Component.literal("Notifications"));
 
         Minecraft client = Minecraft.getInstance();
-        parent = client.screen;
+        parent = client.gui.screen();
         Window window = client.getWindow();
 
         scroll = new ConfigScroll(0,  BUTTON_HEIGHT + TITLE_Y_OFFSET + 16, window.getGuiScaledWidth(), window.getGuiScaledHeight() - BUTTON_HEIGHT, Constants.WIDGET_WIDTH);
@@ -59,7 +59,7 @@ public class NotificationList extends Screen {
     @Override
     public void onClose() {
         Notifications.save();
-        this.minecraft.setScreen(this.parent);
+        this.minecraft.gui.setScreen(this.parent);
     }
 
     private void updateList() {
diff --git a/src/main/java/blade/addon/features/other/PracticeSS.java b/src/main/java/blade/addon/features/other/PracticeSS.java
index decfc62..0f005d6 100644
--- a/src/main/java/blade/addon/features/other/PracticeSS.java
+++ b/src/main/java/blade/addon/features/other/PracticeSS.java
@@ -24,6 +24,7 @@ import net.minecraft.world.level.block.state.BlockState;
 import net.minecraft.world.level.block.state.properties.BlockStateProperties;
 import net.minecraft.world.phys.AABB;
 import net.minecraft.world.phys.BlockHitResult;
+import net.minecraft.world.phys.Vec3;
 
 import java.util.Collections;
 import java.util.List;
@@ -387,7 +388,7 @@ public class PracticeSS {
 
     private static void renderBackground(int backgroundIndex) {
         if (buttons.size() > backgroundIndex && backgroundIndex >= 0) {
-            AABB box = AABB.ofSize(buttons.get(backgroundIndex).getCenter(), 1, 1, 1);
+            AABB box = AABB.ofSize(Vec3.atCenterOf(buttons.get(backgroundIndex)), 1, 1, 1);
             int dx, dz;
             switch (direction) {
                 case SOUTH -> {
diff --git a/src/main/java/blade/addon/mixin/ArrowLayerMixin.java b/src/main/java/blade/addon/mixin/ArrowLayerMixin.java
index 8093c4b..2e3c920 100644
--- a/src/main/java/blade/addon/mixin/ArrowLayerMixin.java
+++ b/src/main/java/blade/addon/mixin/ArrowLayerMixin.java
@@ -2,6 +2,7 @@ package blade.addon.mixin;
 
 import blade.addon.utils.config.values.Visual;
 import net.minecraft.client.renderer.entity.layers.ArrowLayer;
+import net.minecraft.client.renderer.entity.state.AvatarRenderState;
 import org.spongepowered.asm.mixin.Mixin;
 import org.spongepowered.asm.mixin.injection.At;
 import org.spongepowered.asm.mixin.injection.Inject;
@@ -11,7 +12,7 @@ import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
 public class ArrowLayerMixin {
 
     @Inject(method = "numStuck", at=@At("TAIL"), cancellable = true)
-    public void shouldRender(CallbackInfoReturnable<Integer> cir) {
+    public void shouldRender(AvatarRenderState state, CallbackInfoReturnable<Integer> cir) {
         if (Visual.hideStuckArrows) {
             cir.setReturnValue(0);
         }
diff --git a/src/main/java/blade/addon/mixin/ChatScreenMixin.java b/src/main/java/blade/addon/mixin/ChatScreenMixin.java
index d3f4ccf..da8eeeb 100644
--- a/src/main/java/blade/addon/mixin/ChatScreenMixin.java
+++ b/src/main/java/blade/addon/mixin/ChatScreenMixin.java
@@ -28,7 +28,7 @@ public class ChatScreenMixin {
         if (!ExtraOptions.copyChat || click.button() != GLFW.GLFW_MOUSE_BUTTON_RIGHT) return;
 
         Minecraft mc = Minecraft.getInstance();
-        ChatComponent chatHud = mc.gui.getChat();
+        ChatComponent chatHud = mc.gui.hud.getChat();
         ChatComponentMixin hudAccessor = (ChatComponentMixin) chatHud;
         if (chatHud == null) return;
 
diff --git a/src/main/java/blade/addon/mixin/EnderPearlItemMixin.java b/src/main/java/blade/addon/mixin/EnderPearlItemMixin.java
index 811706e..c35ffb9 100644
--- a/src/main/java/blade/addon/mixin/EnderPearlItemMixin.java
+++ b/src/main/java/blade/addon/mixin/EnderPearlItemMixin.java
@@ -4,6 +4,7 @@ import blade.addon.features.other.SwingAnimation;
 import blade.addon.utils.config.values.Visual;
 import blade.addon.utils.data.EntityUtil;
 import net.minecraft.world.InteractionHand;
+import net.minecraft.world.InteractionResult;
 import net.minecraft.world.entity.player.Player;
 import net.minecraft.world.item.EnderpearlItem;
 import net.minecraft.world.level.Level;
@@ -16,7 +17,7 @@ import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
 public class EnderPearlItemMixin {
 
     @Inject(method = "use", at=@At("HEAD"))
-    private void use(Level world, Player user, InteractionHand hand, CallbackInfoReturnable<Boolean> cir) {
+    private void use(Level world, Player user, InteractionHand hand, CallbackInfoReturnable<InteractionResult> cir) {
         if (Visual.stopPearlSwing && EntityUtil.isClientPlayer(user)) {
             SwingAnimation.ignoreNext(2);
         }
diff --git a/src/main/java/blade/addon/mixin/GuiMixin.java b/src/main/java/blade/addon/mixin/GuiMixin.java
index c79cebe..bb9126c 100644
--- a/src/main/java/blade/addon/mixin/GuiMixin.java
+++ b/src/main/java/blade/addon/mixin/GuiMixin.java
@@ -1,96 +1,19 @@
 package blade.addon.mixin;
 
-import blade.addon.features.dungeon.f7.terms.TitleHider;
-import blade.addon.features.dungeon.f7.terms.device.DeviceNotifier;
-import blade.addon.utils.config.components.Components;
-import blade.addon.utils.config.values.ExtraOptions;
-import blade.addon.utils.config.values.Visual;
-import blade.addon.utils.interfaces.GameHud;
-import blade.addon.utils.rendering.DrawEvents;
-import com.llamalad7.mixinextras.sugar.Local;
-import net.minecraft.client.DeltaTracker;
+import blade.addon.utils.events.Events;
+import blade.addon.utils.events.interfaces.ScreenEvent;
 import net.minecraft.client.gui.Gui;
-import net.minecraft.client.gui.GuiGraphicsExtractor;
-import net.minecraft.network.chat.Component;
-import net.minecraft.network.chat.MutableComponent;
-import net.minecraft.util.profiling.Profiler;
-import net.minecraft.world.entity.player.Player;
-import net.minecraft.world.item.ItemStack;
+import net.minecraft.client.gui.screens.Screen;
 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.callback.CallbackInfo;
 
 @Mixin(Gui.class)
-public class GuiMixin implements GameHud {
+public class GuiMixin {
 
-    @Shadow
-    private Component title;
-    @Shadow
-    private Component subtitle;
-    @Shadow
-    private int titleFadeInTime;
-    @Shadow
-    private int titleStayTime;
-    @Shadow
-    private int titleFadeOutTime;
-    @Shadow
-    private int titleTime;
-
-    @Shadow
-    private int toolHighlightTimer;
-
-    @Inject(method = "extractSlot", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/gui/GuiGraphicsExtractor;item(Lnet/minecraft/world/entity/LivingEntity;Lnet/minecraft/world/item/ItemStack;III)V"), order = 2000)
-    public void drawBackground(GuiGraphicsExtractor context, int x, int y, DeltaTracker tickCounter, Player player, ItemStack stack, int seed, CallbackInfo ci) {
-        DrawEvents.HUD_SLOT_BEFORE.invoke(slotEvent -> slotEvent.draw(context, stack, x, y));
-    }
-
-    @Inject(method = "extractSlot", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/gui/GuiGraphicsExtractor;item(Lnet/minecraft/world/entity/LivingEntity;Lnet/minecraft/world/item/ItemStack;III)V", shift = At.Shift.AFTER), order = 2000)
-    public void drawStar(GuiGraphicsExtractor context, int x, int y, DeltaTracker tickCounter, Player player, ItemStack stack, int seed, CallbackInfo ci) {
-        DrawEvents.HUD_SLOT_AFTER.invoke(slotEvent -> slotEvent.draw(context, stack, x, y));
-    }
-
-    @Inject(method = "extractEffects", at = @At("HEAD"), cancellable = true, order = 2000)
-    public void renderStatusEffectsOverLay(GuiGraphicsExtractor context, DeltaTracker tickCounter, CallbackInfo ci) {
-        if (Visual.hideStatusOverLay) {
-            ci.cancel();
-        }
-    }
-
-    @Inject(method = "setTitle", at = @At("HEAD"), cancellable = true, order = 2000)
-    public void setTitle(Component title, CallbackInfo ci) {
-        if (TitleHider.shouldHideTitle(title)) ci.cancel();
-        else if (DeviceNotifier.disableTitles(title)) ci.cancel();
-    }
-
-    @Inject(method = "setSubtitle", at = @At("HEAD"), cancellable = true, order = 2000)
-    public void setSubtitle(Component subtitle, CallbackInfo ci) {
-        if (TitleHider.shouldHideTitle(subtitle)) ci.cancel();
-        else if (DeviceNotifier.disableTitles(subtitle)) ci.cancel();
-    }
-
-    @Inject(method = "extractSelectedItemName", at=@At("HEAD"))
-    private void getFadeDuration(GuiGraphicsExtractor graphics, CallbackInfo ci) {
-        if (!ExtraOptions.moveToolTip) return;
-        Components.toolTipDisplay.setFade(toolHighlightTimer);
-    }
-
-    @Inject(method = "extractSelectedItemName", at= @At(value = "INVOKE", target = "Lnet/minecraft/client/gui/GuiGraphicsExtractor;textWithBackdrop(Lnet/minecraft/client/gui/Font;Lnet/minecraft/network/chat/Component;IIII)V"), cancellable = true)
-    private void renderHeldItemTooltip(GuiGraphicsExtractor graphics, CallbackInfo ci, @Local(name = "str") MutableComponent mutableText, @Local(name = "alpha") int color) {
-        if (!ExtraOptions.moveToolTip) return;
-        ci.cancel();
-        Components.toolTipDisplay.setText(mutableText);
-        Components.toolTipDisplay.setColor(color);
-        Profiler.get().pop();
-    }
-
-
-    @Unique
-    public void blade_addons$forceTitle(Component title, Component subtitle) {
-        this.title = title;
-        this.subtitle = subtitle;
-        this.titleTime = this.titleFadeInTime + this.titleStayTime + this.titleFadeOutTime;
+    @Inject(method = "setScreen", at = @At("HEAD"))
+    public void onClose(Screen screen, CallbackInfo ci) {
+        if (screen == null) Events.ON_SCREEN.invoke(screenEvent -> screenEvent.onScreen(null));
     }
 }
diff --git a/src/main/java/blade/addon/mixin/HudMixin.java b/src/main/java/blade/addon/mixin/HudMixin.java
new file mode 100644
index 0000000..bf44c23
--- /dev/null
+++ b/src/main/java/blade/addon/mixin/HudMixin.java
@@ -0,0 +1,96 @@
+package blade.addon.mixin;
+
+import blade.addon.features.dungeon.f7.terms.TitleHider;
+import blade.addon.features.dungeon.f7.terms.device.DeviceNotifier;
+import blade.addon.utils.config.components.Components;
+import blade.addon.utils.config.values.ExtraOptions;
+import blade.addon.utils.config.values.Visual;
+import blade.addon.utils.interfaces.GameHud;
+import blade.addon.utils.rendering.DrawEvents;
+import com.llamalad7.mixinextras.sugar.Local;
+import net.minecraft.client.DeltaTracker;
+import net.minecraft.client.gui.GuiGraphicsExtractor;
+import net.minecraft.client.gui.Hud;
+import net.minecraft.network.chat.Component;
+import net.minecraft.network.chat.MutableComponent;
+import net.minecraft.util.profiling.Profiler;
+import net.minecraft.world.entity.player.Player;
+import net.minecraft.world.item.ItemStack;
+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.callback.CallbackInfo;
+
+@Mixin(Hud.class)
+public class HudMixin implements GameHud {
+
+    @Shadow
+    private Component title;
+    @Shadow
+    private Component subtitle;
+    @Shadow
+    private int titleFadeInTime;
+    @Shadow
+    private int titleStayTime;
+    @Shadow
+    private int titleFadeOutTime;
+    @Shadow
+    private int titleTime;
+
+    @Shadow
+    private int toolHighlightTimer;
+
+    @Inject(method = "extractSlot", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/gui/GuiGraphicsExtractor;item(Lnet/minecraft/world/entity/LivingEntity;Lnet/minecraft/world/item/ItemStack;III)V"), order = 2000)
+    public void drawBackground(GuiGraphicsExtractor context, int x, int y, DeltaTracker tickCounter, Player player, ItemStack stack, int seed, CallbackInfo ci) {
+        DrawEvents.HUD_SLOT_BEFORE.invoke(slotEvent -> slotEvent.draw(context, stack, x, y));
+    }
+
+    @Inject(method = "extractSlot", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/gui/GuiGraphicsExtractor;item(Lnet/minecraft/world/entity/LivingEntity;Lnet/minecraft/world/item/ItemStack;III)V", shift = At.Shift.AFTER), order = 2000)
+    public void drawStar(GuiGraphicsExtractor context, int x, int y, DeltaTracker tickCounter, Player player, ItemStack stack, int seed, CallbackInfo ci) {
+        DrawEvents.HUD_SLOT_AFTER.invoke(slotEvent -> slotEvent.draw(context, stack, x, y));
+    }
+
+    @Inject(method = "extractEffects", at = @At("HEAD"), cancellable = true, order = 2000)
+    public void renderStatusEffectsOverLay(GuiGraphicsExtractor context, DeltaTracker tickCounter, CallbackInfo ci) {
+        if (Visual.hideStatusOverLay) {
+            ci.cancel();
+        }
+    }
+
+    @Inject(method = "setTitle", at = @At("HEAD"), cancellable = true, order = 2000)
+    public void setTitle(Component title, CallbackInfo ci) {
+        if (TitleHider.shouldHideTitle(title)) ci.cancel();
+        else if (DeviceNotifier.disableTitles(title)) ci.cancel();
+    }
+
+    @Inject(method = "setSubtitle", at = @At("HEAD"), cancellable = true, order = 2000)
+    public void setSubtitle(Component subtitle, CallbackInfo ci) {
+        if (TitleHider.shouldHideTitle(subtitle)) ci.cancel();
+        else if (DeviceNotifier.disableTitles(subtitle)) ci.cancel();
+    }
+
+    @Inject(method = "extractSelectedItemName", at=@At("HEAD"))
+    private void getFadeDuration(GuiGraphicsExtractor graphics, CallbackInfo ci) {
+        if (!ExtraOptions.moveToolTip) return;
+        Components.toolTipDisplay.setFade(toolHighlightTimer);
+    }
+
+    @Inject(method = "extractSelectedItemName", at= @At(value = "INVOKE", target = "Lnet/minecraft/client/gui/GuiGraphicsExtractor;textWithBackdrop(Lnet/minecraft/client/gui/Font;Lnet/minecraft/network/chat/Component;IIII)V"), cancellable = true)
+    private void renderHeldItemTooltip(GuiGraphicsExtractor graphics, CallbackInfo ci, @Local(name = "str") MutableComponent mutableText, @Local(name = "alpha") int color) {
+        if (!ExtraOptions.moveToolTip) return;
+        ci.cancel();
+        Components.toolTipDisplay.setText(mutableText);
+        Components.toolTipDisplay.setColor(color);
+        Profiler.get().pop();
+    }
+
+
+    @Unique
+    public void blade_addons$forceTitle(Component title, Component subtitle) {
+        this.title = title;
+        this.subtitle = subtitle;
+        this.titleTime = this.titleFadeInTime + this.titleStayTime + this.titleFadeOutTime;
+    }
+}
diff --git a/src/main/java/blade/addon/mixin/MinecraftMixin.java b/src/main/java/blade/addon/mixin/MinecraftMixin.java
index 8f4206f..78a64f8 100644
--- a/src/main/java/blade/addon/mixin/MinecraftMixin.java
+++ b/src/main/java/blade/addon/mixin/MinecraftMixin.java
@@ -5,7 +5,6 @@ import blade.addon.utils.events.Events;
 import blade.addon.utils.events.interfaces.WorldEvent;
 import com.llamalad7.mixinextras.sugar.Local;
 import net.minecraft.client.Minecraft;
-import net.minecraft.client.gui.screens.Screen;
 import net.minecraft.client.multiplayer.ClientLevel;
 import net.minecraft.world.item.ItemStack;
 import net.minecraft.world.phys.BlockHitResult;
@@ -43,9 +42,4 @@ public class MinecraftMixin {
     private void onWorld(ClientLevel world, CallbackInfo ci) {
         Events.ON_WORLD_CHANGE.invoke(WorldEvent::onWorldSwap);
     }
-
-    @Inject(method = "setScreen", at=@At("HEAD"))
-    public void onClose(Screen screen, CallbackInfo ci) {
-        if (screen == null) Events.ON_SCREEN.invoke(screenEvent -> screenEvent.onScreen(null));
-    }
 }
diff --git a/src/main/java/blade/addon/utils/Keybinds.java b/src/main/java/blade/addon/utils/Keybinds.java
index 3c669b8..43d75b3 100644
--- a/src/main/java/blade/addon/utils/Keybinds.java
+++ b/src/main/java/blade/addon/utils/Keybinds.java
@@ -85,7 +85,7 @@ public class Keybinds {
     public static void checkInputs(Minecraft client) {
 
         if (openConfig.consumeClick()) {
-            client.setScreen(Config.createScreen(null));
+            client.gui.setScreen(Config.createScreen(null));
         }
 
         if (trades.consumeClick()) {
diff --git a/src/main/java/blade/addon/utils/Misc.java b/src/main/java/blade/addon/utils/Misc.java
index af721f4..dc315b3 100644
--- a/src/main/java/blade/addon/utils/Misc.java
+++ b/src/main/java/blade/addon/utils/Misc.java
@@ -40,7 +40,7 @@ public class Misc {
         try {
             if (INSTANCE == null) return;
             Gui gameHud = INSTANCE.gui;
-            ChatComponent hud = gameHud.getChat();
+            ChatComponent hud = gameHud.hud.getChat();
             forceMainThread(() -> hud.addClientSystemMessage(Component.literal(ExtraOptions.textPrefix).append(text)));
         } catch (IndexOutOfBoundsException ignored) {
             Debug.LOGGER.error("Chat message failed to get added");
@@ -52,7 +52,7 @@ public class Misc {
     }
 
     public static void setTitle(Component text) {
-        forceMainThread(() -> INSTANCE.gui.setTitle(text));
+        forceMainThread(() -> INSTANCE.gui.hud.setTitle(text));
     }
 
     public static void forceTitle(Component title, Component subtitle) {
diff --git a/src/main/java/blade/addon/utils/Scheduler.java b/src/main/java/blade/addon/utils/Scheduler.java
index f2f406d..21460ce 100644
--- a/src/main/java/blade/addon/utils/Scheduler.java
+++ b/src/main/java/blade/addon/utils/Scheduler.java
@@ -32,7 +32,7 @@ public class Scheduler {
             if (scheduledScreen != null) {
                 screenTicks--;
                 if (screenTicks <= 0) {
-                    minecraftClient.setScreen(scheduledScreen);
+                    minecraftClient.gui.setScreen(scheduledScreen);
                     scheduledScreen = null;
                 }
             }
diff --git a/src/main/java/blade/addon/utils/config/Config.java b/src/main/java/blade/addon/utils/config/Config.java
index 4d2c08f..d830da4 100644
--- a/src/main/java/blade/addon/utils/config/Config.java
+++ b/src/main/java/blade/addon/utils/config/Config.java
@@ -53,13 +53,13 @@ public class Config {
         general.add(new ConfigString(Component.literal("Message prefix"), () -> ExtraOptions.textPrefix, str -> ExtraOptions.textPrefix = str));
 
         ConfigSection notifications = new ConfigSection(Component.literal("Chat Notifications"));
-        notifications.add(new ConfigButton(Component.literal("Edit Notifications"), () -> Minecraft.getInstance().setScreen(new NotificationList())));
+        notifications.add(new ConfigButton(Component.literal("Edit Notifications"), () -> Minecraft.getInstance().gui.setScreen(new NotificationList())));
         notifications.add(new ConfigBool(Component.literal("Ignore color codes"), () -> ExtraOptions.ignoreColorCodesNotification, bool -> ExtraOptions.ignoreColorCodesNotification = bool));
         general.add(notifications);
 
 
         ConfigSection chatFilter = new ConfigSection(Component.literal("Chat filter"));
-        chatFilter.add(new ConfigButton(Component.literal("Edit"), () -> Minecraft.getInstance().setScreen(new FilterList())));
+        chatFilter.add(new ConfigButton(Component.literal("Edit"), () -> Minecraft.getInstance().gui.setScreen(new FilterList())));
         chatFilter.add(new ConfigBool(Component.literal("Ignore color codes"), () -> ExtraOptions.ignoreColorCodesFilter, bool -> ExtraOptions.ignoreColorCodesFilter = bool));
         chatFilter.add(new ConfigBool(Component.literal("Disable all filters"), () -> ExtraOptions.disableAllFilters, bool -> ExtraOptions.disableAllFilters = bool));
         general.add(chatFilter);
diff --git a/src/main/java/blade/addon/utils/data/TextUtil.java b/src/main/java/blade/addon/utils/data/TextUtil.java
index 921f8f0..0979914 100644
--- a/src/main/java/blade/addon/utils/data/TextUtil.java
+++ b/src/main/java/blade/addon/utils/data/TextUtil.java
@@ -106,11 +106,11 @@ public class TextUtil {
 
     private static char getFormatChar(int color) {
         for (ChatFormatting format: ChatFormatting.values()) {
-            Integer colorValue = format.getColor();
-            if (colorValue == null) continue;
+            TextColor textColor = TextColor.fromLegacyFormat(format);
+            if (textColor == null) continue;
 
-            if (colorValue == color) {
-                return format.getChar();
+            if (textColor.getValue() == color) {
+                return format.toString().charAt(1);
             }
         }
 
diff --git a/src/main/java/blade/addon/utils/rendering/RenderHandler.java b/src/main/java/blade/addon/utils/rendering/RenderHandler.java
index 37f2f5e..53ddff1 100644
--- a/src/main/java/blade/addon/utils/rendering/RenderHandler.java
+++ b/src/main/java/blade/addon/utils/rendering/RenderHandler.java
@@ -3,7 +3,6 @@ package blade.addon.utils.rendering;
 import com.mojang.blaze3d.vertex.PoseStack;
 import com.mojang.blaze3d.vertex.VertexConsumer;
 import net.fabricmc.fabric.api.client.rendering.v1.level.LevelRenderContext;
-import net.minecraft.client.renderer.MultiBufferSource;
 import net.minecraft.client.renderer.rendertype.RenderType;
 import net.minecraft.client.renderer.state.level.LevelRenderState;
 import net.minecraft.world.phys.Vec3;
@@ -39,15 +38,76 @@ public class RenderHandler {
         Vec3 camera = worldState.cameraRenderState.pos;
         PoseStack matrices = context.poseStack();
         if (matrices == null) return;
+        if (listeners.isEmpty()) return;
         matrices.pushPose();
         matrices.translate(-camera.x, -camera.y, -camera.z);
 
-        MultiBufferSource consumers = context.bufferSource();
-        if (consumers == null) return;
-        VertexConsumer consumer = consumers.getBuffer(renderType);
+        // Listeners run during the submit-collection phase (they also submit Gizmos and text, which
+        // are only accepted while collecting). Their vertex output is recorded and replayed into the
+        // real per-frame consumer when the custom geometry submit is drawn.
+        RecordingVertexConsumer recording = new RecordingVertexConsumer();
+        invoke(renderingEvent -> renderingEvent.render(context, matrices, recording));
 
-        invoke(renderingEvent -> renderingEvent.render(context, matrices, consumer));
+        context.submitNodeCollector().submitCustomGeometry(matrices, renderType, (pose, consumer) -> recording.replay(consumer));
         matrices.popPose();
     }
 
+    private static final class RecordingVertexConsumer implements VertexConsumer {
+        private final List<Consumer<VertexConsumer>> ops = new ArrayList<>();
+
+        @Override
+        public VertexConsumer addVertex(float x, float y, float z) {
+            ops.add(consumer -> consumer.addVertex(x, y, z));
+            return this;
+        }
+
+        @Override
+        public VertexConsumer setColor(int r, int g, int b, int a) {
+            ops.add(consumer -> consumer.setColor(r, g, b, a));
+            return this;
+        }
+
+        @Override
+        public VertexConsumer setColor(int rgba) {
+            ops.add(consumer -> consumer.setColor(rgba));
+            return this;
+        }
+
+        @Override
+        public VertexConsumer setUv(float u, float v) {
+            ops.add(consumer -> consumer.setUv(u, v));
+            return this;
+        }
+
+        @Override
+        public VertexConsumer setUv1(int u, int v) {
+            ops.add(consumer -> consumer.setUv1(u, v));
+            return this;
+        }
+
+        @Override
+        public VertexConsumer setUv2(int u, int v) {
+            ops.add(consumer -> consumer.setUv2(u, v));
+            return this;
+        }
+
+        @Override
+        public VertexConsumer setNormal(float x, float y, float z) {
+            ops.add(consumer -> consumer.setNormal(x, y, z));
+            return this;
+        }
+
+        @Override
+        public VertexConsumer setLineWidth(float width) {
+            ops.add(consumer -> consumer.setLineWidth(width));
+            return this;
+        }
+
+        private void replay(VertexConsumer consumer) {
+            for (Consumer<VertexConsumer> op : ops) {
+                op.accept(consumer);
+            }
+        }
+    }
+
 }
diff --git a/src/main/java/blade/addon/utils/rendering/RenderPipelines.java b/src/main/java/blade/addon/utils/rendering/RenderPipelines.java
index d082cea..147fca3 100644
--- a/src/main/java/blade/addon/utils/rendering/RenderPipelines.java
+++ b/src/main/java/blade/addon/utils/rendering/RenderPipelines.java
@@ -1,56 +1,96 @@
 package blade.addon.utils.rendering;
 
 import blade.addon.utils.Constants;
+import com.mojang.blaze3d.PrimitiveTopology;
+import com.mojang.blaze3d.pipeline.BlendFunction;
+import com.mojang.blaze3d.pipeline.ColorTargetState;
 import com.mojang.blaze3d.pipeline.DepthStencilState;
 import com.mojang.blaze3d.pipeline.RenderPipeline;
 import com.mojang.blaze3d.platform.CompareOp;
 import com.mojang.blaze3d.vertex.DefaultVertexFormat;
-import com.mojang.blaze3d.vertex.VertexFormat;
+import net.minecraft.client.renderer.BindGroupLayouts;
 import net.minecraft.resources.Identifier;
 
 public class RenderPipelines {
-    public static final RenderPipeline FILLED_DEBUG = net.minecraft.client.renderer.RenderPipelines.register(RenderPipeline.builder(net.minecraft.client.renderer.RenderPipelines.DEBUG_FILLED_SNIPPET)
+
+    // Vanilla's DEBUG_FILLED_SNIPPET / LINES_SNIPPET are private in 26.2; rebuilt here with identical
+    // composition (see net.minecraft.client.renderer.RenderPipelines static initializer).
+    private static final RenderPipeline.Snippet GLOBALS_SNIPPET = RenderPipeline.builder()
+            .withBindGroupLayout(BindGroupLayouts.GLOBALS)
+            .buildSnippet();
+
+    private static final RenderPipeline.Snippet DEBUG_FILLED_SNIPPET = RenderPipeline.builder(GLOBALS_SNIPPET)
+            .withBindGroupLayout(BindGroupLayouts.MATRICES_PROJECTION)
+            .withVertexShader("core/position_color")
+            .withFragmentShader("core/position_color")
+            .withColorTargetState(new ColorTargetState(BlendFunction.TRANSLUCENT))
+            .withVertexBinding(0, DefaultVertexFormat.POSITION_COLOR)
+            .withPrimitiveTopology(PrimitiveTopology.QUADS)
+            .withDepthStencilState(new DepthStencilState(CompareOp.GREATER_THAN_OR_EQUAL, false))
+            .withCull(false)
+            .buildSnippet();
+
+    private static final RenderPipeline.Snippet LINES_SNIPPET = RenderPipeline.builder(GLOBALS_SNIPPET)
+            .withBindGroupLayout(BindGroupLayouts.MATRICES_PROJECTION)
+            .withBindGroupLayout(BindGroupLayouts.FOG)
+            .withVertexShader("core/rendertype_lines")
+            .withFragmentShader("core/rendertype_lines")
+            .withColorTargetState(new ColorTargetState(BlendFunction.TRANSLUCENT))
+            .withCull(false)
+            .withVertexBinding(0, DefaultVertexFormat.POSITION_COLOR_NORMAL_LINE_WIDTH)
+            .withPrimitiveTopology(PrimitiveTopology.LINES)
+            .withDepthStencilState(DepthStencilState.DEFAULT)
+            .buildSnippet();
+
+    public static final RenderPipeline FILLED_DEBUG = net.minecraft.client.renderer.RenderPipelines.register(RenderPipeline.builder(DEBUG_FILLED_SNIPPET)
             .withLocation(Identifier.fromNamespaceAndPath(Constants.NAMESPACE, "filled-debug-pipe"))
-            .withVertexFormat(DefaultVertexFormat.POSITION_COLOR, VertexFormat.Mode.QUADS)
+            .withVertexBinding(0, DefaultVertexFormat.POSITION_COLOR)
+            .withPrimitiveTopology(PrimitiveTopology.QUADS)
             .withDepthStencilState(DepthStencilState.DEFAULT)
             .build());
 
-    public static final RenderPipeline FILLED_DEBUG_NO_DEPTH = net.minecraft.client.renderer.RenderPipelines.register(RenderPipeline.builder(net.minecraft.client.renderer.RenderPipelines.DEBUG_FILLED_SNIPPET)
+    public static final RenderPipeline FILLED_DEBUG_NO_DEPTH = net.minecraft.client.renderer.RenderPipelines.register(RenderPipeline.builder(DEBUG_FILLED_SNIPPET)
             .withLocation(Identifier.fromNamespaceAndPath(Constants.NAMESPACE, "filled-debug-no-depth-pipe"))
-            .withVertexFormat(DefaultVertexFormat.POSITION_COLOR, VertexFormat.Mode.QUADS)
+            .withVertexBinding(0, DefaultVertexFormat.POSITION_COLOR)
+            .withPrimitiveTopology(PrimitiveTopology.QUADS)
             .build());
 
 
 
-    public static final RenderPipeline FILLED = net.minecraft.client.renderer.RenderPipelines.register(RenderPipeline.builder(net.minecraft.client.renderer.RenderPipelines.DEBUG_FILLED_SNIPPET)
+    public static final RenderPipeline FILLED = net.minecraft.client.renderer.RenderPipelines.register(RenderPipeline.builder(DEBUG_FILLED_SNIPPET)
             .withLocation(Identifier.fromNamespaceAndPath(Constants.NAMESPACE, "filled-pipe"))
-            .withVertexFormat(DefaultVertexFormat.POSITION_COLOR, VertexFormat.Mode.QUADS)
+            .withVertexBinding(0, DefaultVertexFormat.POSITION_COLOR)
+            .withPrimitiveTopology(PrimitiveTopology.QUADS)
             .withDepthStencilState(DepthStencilState.DEFAULT)
             .build());
 
-    public static final RenderPipeline LINE = net.minecraft.client.renderer.RenderPipelines.register(RenderPipeline.builder(net.minecraft.client.renderer.RenderPipelines.LINES_SNIPPET)
+    public static final RenderPipeline LINE = net.minecraft.client.renderer.RenderPipelines.register(RenderPipeline.builder(LINES_SNIPPET)
             .withLocation(Identifier.fromNamespaceAndPath(Constants.NAMESPACE, "line-pipe"))
-            .withVertexFormat(DefaultVertexFormat.POSITION_COLOR_NORMAL_LINE_WIDTH, VertexFormat.Mode.LINES)
+            .withVertexBinding(0, DefaultVertexFormat.POSITION_COLOR_NORMAL_LINE_WIDTH)
+            .withPrimitiveTopology(PrimitiveTopology.LINES)
             .withDepthStencilState(DepthStencilState.DEFAULT)
             .build());
 
 
-    public static final RenderPipeline FILLED_NO_DEPTH = net.minecraft.client.renderer.RenderPipelines.register(RenderPipeline.builder(net.minecraft.client.renderer.RenderPipelines.DEBUG_FILLED_SNIPPET)
+    public static final RenderPipeline FILLED_NO_DEPTH = net.minecraft.client.renderer.RenderPipelines.register(RenderPipeline.builder(DEBUG_FILLED_SNIPPET)
             .withLocation(Identifier.fromNamespaceAndPath(Constants.NAMESPACE, "filled-no-depth-pipe"))
-            .withVertexFormat(DefaultVertexFormat.POSITION_COLOR, VertexFormat.Mode.QUADS)
+            .withVertexBinding(0, DefaultVertexFormat.POSITION_COLOR)
+            .withPrimitiveTopology(PrimitiveTopology.QUADS)
             .withDepthStencilState(new DepthStencilState(CompareOp.ALWAYS_PASS, false))
             .build());
 
 
-    public static final RenderPipeline LINE_NO_DEPTH = net.minecraft.client.renderer.RenderPipelines.register(RenderPipeline.builder(net.minecraft.client.renderer.RenderPipelines.LINES_SNIPPET)
+    public static final RenderPipeline LINE_NO_DEPTH = net.minecraft.client.renderer.RenderPipelines.register(RenderPipeline.builder(LINES_SNIPPET)
             .withLocation(Identifier.fromNamespaceAndPath(blade.addon.utils.Constants.NAMESPACE, "line-no-depth-pipe"))
-            .withVertexFormat(DefaultVertexFormat.POSITION_COLOR_NORMAL_LINE_WIDTH, VertexFormat.Mode.LINES)
+            .withVertexBinding(0, DefaultVertexFormat.POSITION_COLOR_NORMAL_LINE_WIDTH)
+            .withPrimitiveTopology(PrimitiveTopology.LINES)
             .withDepthStencilState(new DepthStencilState(CompareOp.ALWAYS_PASS, false))
             .build());
 
-    public static final RenderPipeline TEXT = net.minecraft.client.renderer.RenderPipelines.register(RenderPipeline.builder(net.minecraft.client.renderer.RenderPipelines.LINES_SNIPPET)
+    public static final RenderPipeline TEXT = net.minecraft.client.renderer.RenderPipelines.register(RenderPipeline.builder(LINES_SNIPPET)
             .withLocation(Identifier.fromNamespaceAndPath(blade.addon.utils.Constants.NAMESPACE, "text-pipe"))
-            .withVertexFormat(DefaultVertexFormat.POSITION_COLOR_NORMAL_LINE_WIDTH, VertexFormat.Mode.LINES)
+            .withVertexBinding(0, DefaultVertexFormat.POSITION_COLOR_NORMAL_LINE_WIDTH)
+            .withPrimitiveTopology(PrimitiveTopology.LINES)
             .withDepthStencilState(new DepthStencilState(CompareOp.ALWAYS_PASS, false))
             .build());
 }
diff --git a/src/main/java/blade/addon/utils/rendering/RenderUtils.java b/src/main/java/blade/addon/utils/rendering/RenderUtils.java
index 3c34143..87f7df3 100644
--- a/src/main/java/blade/addon/utils/rendering/RenderUtils.java
+++ b/src/main/java/blade/addon/utils/rendering/RenderUtils.java
@@ -103,7 +103,7 @@ public class RenderUtils {
 
         float halfWidth = textRenderer.width(text.getString()) / 2f;
 
-        textRenderer.drawInBatch(text, -halfWidth, 0, 0xffffffff, true, matrices.last().pose(), context.bufferSource(), Font.DisplayMode.SEE_THROUGH, 0, 15728880);
+        context.submitNodeCollector().submitText(matrices, -halfWidth, 0, text.getVisualOrderText(), true, Font.DisplayMode.SEE_THROUGH, 15728880, 0xffffffff, 0, 0);
         matrices.popPose();
     }
 
diff --git a/src/main/java/blade/addon/utils/rendering/RenderingEvents.java b/src/main/java/blade/addon/utils/rendering/RenderingEvents.java
index b079839..7244035 100644
--- a/src/main/java/blade/addon/utils/rendering/RenderingEvents.java
+++ b/src/main/java/blade/addon/utils/rendering/RenderingEvents.java
@@ -14,12 +14,14 @@ public class RenderingEvents {
 
 
     public static void init() {
+        // 26.2 only accepts submits while the node collection is open; AFTER_TRANSLUCENT_FEATURES fires
+        // during the draw passes, after the collection has been prepared, so everything must register here.
         LevelRenderEvents.COLLECT_SUBMITS.register(FILLED_DEBUG::init);
         LevelRenderEvents.COLLECT_SUBMITS.register(FILLED_DEBUG_NO_DEPTH::init);
-        LevelRenderEvents.AFTER_TRANSLUCENT_FEATURES.register(FILLED::init);
-        LevelRenderEvents.AFTER_TRANSLUCENT_FEATURES.register(LINE::init);
-        LevelRenderEvents.AFTER_TRANSLUCENT_FEATURES.register(FILLED_NO_DEPTH::init);
-        LevelRenderEvents.AFTER_TRANSLUCENT_FEATURES.register(LINE_NO_DEPTH::init);
-        LevelRenderEvents.AFTER_TRANSLUCENT_FEATURES.register(TEXT::init);
+        LevelRenderEvents.COLLECT_SUBMITS.register(FILLED::init);
+        LevelRenderEvents.COLLECT_SUBMITS.register(LINE::init);
+        LevelRenderEvents.COLLECT_SUBMITS.register(FILLED_NO_DEPTH::init);
+        LevelRenderEvents.COLLECT_SUBMITS.register(LINE_NO_DEPTH::init);
+        LevelRenderEvents.COLLECT_SUBMITS.register(TEXT::init);
     }
 }
diff --git a/src/main/resources/blade-addons.mixins.json b/src/main/resources/blade-addons.mixins.json
index 8ddb782..cab3807 100644
--- a/src/main/resources/blade-addons.mixins.json
+++ b/src/main/resources/blade-addons.mixins.json
@@ -32,6 +32,7 @@
     "GuiGraphicsExtractorMixin",
     "GuiMixin",
     "HeldItemRendererMixin",
+    "HudMixin",
     "InventoryScreenMixin",
     "KeyboardHandlerMixin",
     "LocalPlayerMixin",
diff --git a/src/main/resources/fabric.mod.json b/src/main/resources/fabric.mod.json
index 183540d..b63b8b0 100644
--- a/src/main/resources/fabric.mod.json
+++ b/src/main/resources/fabric.mod.json
@@ -27,10 +27,9 @@
 	],
 	"depends": {
 		"fabricloader": ">=0.19.3",
-		"minecraft": "~26.1.2",
+		"minecraft": "~26.2",
 		"java": ">=25",
-		"fabric-api": "*",
-      "hypixel-mod-api": "*"
+		"fabric-api": "*"
 	},
 	"suggests": {
 		"another-mod": "*"
- fixed splits - fixed capitalisation in rerollblockerGabriel Ellnebrand · 2026-09-15 · db58d61
查看变更
src/main/java/blade/addon/features/dungeon/RerollBlocker.java |  2 +-
 src/main/resources/data/splits.json                           | 88 ++++++++++++++++++++++++++++++++++++++++++++--------------------------------------------
 2 files changed, 45 insertions(+), 45 deletions(-)
diff --git a/src/main/java/blade/addon/features/dungeon/RerollBlocker.java b/src/main/java/blade/addon/features/dungeon/RerollBlocker.java
index 947cd66..8dbec98 100644
--- a/src/main/java/blade/addon/features/dungeon/RerollBlocker.java
+++ b/src/main/java/blade/addon/features/dungeon/RerollBlocker.java
@@ -14,7 +14,7 @@ import net.minecraft.world.item.Items;
 
 public class RerollBlocker {
 
-    private static final String[] BLOCKED_REROLLS = {"Recombobulator ", "Wither Shield", "Implosion", "Shadow Warp", "Necron's Handle", "Star", "Dark Claymore", "Dye", "Giant's Sword", "Shadow fury"};
+    private static final String[] BLOCKED_REROLLS = {"Recombobulator ", "Wither Shield", "Implosion", "Shadow Warp", "Necron's Handle", "Star", "Dark Claymore", "Dye", "Giant's Sword", "Shadow Fury"};
 
     private static boolean blockClick = false;
     private static int containerId = Integer.MIN_VALUE;
diff --git a/src/main/resources/data/splits.json b/src/main/resources/data/splits.json
index eb18916..0a2e20c 100644
--- a/src/main/resources/data/splits.json
+++ b/src/main/resources/data/splits.json
@@ -2,7 +2,7 @@
   "E": [
     {
       "name": "Blood Open",
-      "start": "§e[NPC] §bMort§f: Here, I found this map when I first entered the dungeon.",
+      "start": "[NPC] Mort: Here, I found this map when I first entered the dungeon.",
       "end": "[BOSS] The Watcher: Congratulations, you made it through the Entrance.",
       "color": 11141120
     },
@@ -14,7 +14,7 @@
     },
     {
       "name": "Run Time",
-      "start": "§e[NPC] §bMort§f: Here, I found this map when I first entered the dungeon.",
+      "start": "[NPC] Mort: Here, I found this map when I first entered the dungeon.",
       "end": "some dummy string",
       "color": 4279733527
     }
@@ -22,7 +22,7 @@
   "F1": [
     {
       "name": "Blood Open",
-      "start": "§e[NPC] §bMort§f: Here, I found this map when I first entered the dungeon.",
+      "start": "[NPC] Mort: Here, I found this map when I first entered the dungeon.",
       "end": "[BOSS] The Watcher: Ah, you've finally arrived.",
       "color": 11141120
     },
@@ -40,7 +40,7 @@
     },
     {
       "name": "Boss Entry",
-      "start": "§e[NPC] §bMort§f: Here, I found this map when I first entered the dungeon.",
+      "start": "[NPC] Mort: Here, I found this map when I first entered the dungeon.",
       "end": "[BOSS] Bonzo: Gratz for making it this far, but I'm basically unbeatable.",
       "color": 5592575
     },
@@ -52,7 +52,7 @@
     },
     {
       "name": "Run Time",
-      "start": "§e[NPC] §bMort§f: Here, I found this map when I first entered the dungeon.",
+      "start": "[NPC] Mort: Here, I found this map when I first entered the dungeon.",
       "end": "some dummy string",
       "color": 4279733527
     }
@@ -60,7 +60,7 @@
   "M1": [
     {
       "name": "Blood Open",
-      "start": "§e[NPC] §bMort§f: Here, I found this map when I first entered the dungeon.",
+      "start": "[NPC] Mort: Here, I found this map when I first entered the dungeon.",
       "end": "[BOSS] The Watcher: Ah, you've finally arrived.",
       "color": 11141120
     },
@@ -78,7 +78,7 @@
     },
     {
       "name": "Boss Entry",
-      "start": "§e[NPC] §bMort§f: Here, I found this map when I first entered the dungeon.",
+      "start": "[NPC] Mort: Here, I found this map when I first entered the dungeon.",
       "end": "[BOSS] Bonzo: Gratz for making it this far, but I'm basically unbeatable.",
       "color": 5592575
     },
@@ -90,7 +90,7 @@
     },
     {
       "name": "Run Time",
-      "start": "§e[NPC] §bMort§f: Here, I found this map when I first entered the dungeon.",
+      "start": "[NPC] Mort: Here, I found this map when I first entered the dungeon.",
       "end": "some dummy string",
       "color": 4279733527
     }
@@ -98,7 +98,7 @@
   "F2": [
     {
       "name": "Blood Open",
-      "start": "§e[NPC] §bMort§f: Here, I found this map when I first entered the dungeon.",
+      "start": "[NPC] Mort: Here, I found this map when I first entered the dungeon.",
       "end": "[BOSS] The Watcher: Ah, we meet again...",
       "color": 11141120
     },
@@ -116,7 +116,7 @@
     },
     {
       "name": "Boss Entry",
-      "start": "§e[NPC] §bMort§f: Here, I found this map when I first entered the dungeon.",
+      "start": "[NPC] Mort: Here, I found this map when I first entered the dungeon.",
       "end": "[BOSS] Scarf: This is where the journey ends for you, Adventurers.",
       "color": 5592575
     },
@@ -128,7 +128,7 @@
     },
     {
       "name": "Run Time",
-      "start": "§e[NPC] §bMort§f: Here, I found this map when I first entered the dungeon.",
+      "start": "[NPC] Mort: Here, I found this map when I first entered the dungeon.",
       "end": "some dummy string",
       "color": 4279733527
     }
@@ -136,7 +136,7 @@
   "M2": [
     {
       "name": "Blood Open",
-      "start": "§e[NPC] §bMort§f: Here, I found this map when I first entered the dungeon.",
+      "start": "[NPC] Mort: Here, I found this map when I first entered the dungeon.",
       "end": "[BOSS] The Watcher: Ah, we meet again...",
       "color": 11141120
     },
@@ -154,7 +154,7 @@
     },
     {
       "name": "Boss Entry",
-      "start": "§e[NPC] §bMort§f: Here, I found this map when I first entered the dungeon.",
+      "start": "[NPC] Mort: Here, I found this map when I first entered the dungeon.",
       "end": "[BOSS] Scarf: This is where the journey ends for you, Adventurers.",
       "color": 5592575
     },
@@ -166,7 +166,7 @@
     },
     {
       "name": "Run Time",
-      "start": "§e[NPC] §bMort§f: Here, I found this map when I first entered the dungeon.",
+      "start": "[NPC] Mort: Here, I found this map when I first entered the dungeon.",
       "end": "some dummy string",
       "color": 4279733527
     }
@@ -174,7 +174,7 @@
   "F3": [
     {
       "name": "Blood Open",
-      "start": "§e[NPC] §bMort§f: Here, I found this map when I first entered the dungeon.",
+      "start": "[NPC] Mort: Here, I found this map when I first entered the dungeon.",
       "end": "[BOSS] The Watcher: So you made it this far... interesting.",
       "color": 11141120
     },
@@ -192,7 +192,7 @@
     },
     {
       "name": "Boss Entry",
-      "start": "§e[NPC] §bMort§f: Here, I found this map when I first entered the dungeon.",
+      "start": "[NPC] Mort: Here, I found this map when I first entered the dungeon.",
       "end": "[BOSS] The Professor: I was burdened with terrible news recently...",
       "color": 5592575
     },
@@ -204,7 +204,7 @@
     },
     {
       "name": "Run Time",
-      "start": "§e[NPC] §bMort§f: Here, I found this map when I first entered the dungeon.",
+      "start": "[NPC] Mort: Here, I found this map when I first entered the dungeon.",
       "end": "some dummy string",
       "color": 4279733527
     }
@@ -212,7 +212,7 @@
   "M3": [
     {
       "name": "Blood Open",
-      "start": "§e[NPC] §bMort§f: Here, I found this map when I first entered the dungeon.",
+      "start": "[NPC] Mort: Here, I found this map when I first entered the dungeon.",
       "end": "[BOSS] The Watcher: So you made it this far... interesting.",
       "color": 11141120
     },
@@ -230,7 +230,7 @@
     },
     {
       "name": "Boss Entry",
-      "start": "§e[NPC] §bMort§f: Here, I found this map when I first entered the dungeon.",
+      "start": "[NPC] Mort: Here, I found this map when I first entered the dungeon.",
       "end": "[BOSS] The Professor: I was burdened with terrible news recently...",
       "color": 5592575
     },
@@ -242,7 +242,7 @@
     },
     {
       "name": "Run Time",
-      "start": "§e[NPC] §bMort§f: Here, I found this map when I first entered the dungeon.",
+      "start": "[NPC] Mort: Here, I found this map when I first entered the dungeon.",
       "end": "some dummy string",
       "color": 4279733527
     }
@@ -250,7 +250,7 @@
   "F4": [
     {
       "name": "Blood Open",
-      "start": "§e[NPC] §bMort§f: Here, I found this map when I first entered the dungeon.",
+      "start": "[NPC] Mort: Here, I found this map when I first entered the dungeon.",
       "end": "[BOSS] The Watcher: You've managed to scratch and claw your way here, eh?",
       "color": 11141120
     },
@@ -268,7 +268,7 @@
     },
     {
       "name": "Boss Entry",
-      "start": "§e[NPC] §bMort§f: Here, I found this map when I first entered the dungeon.",
+      "start": "[NPC] Mort: Here, I found this map when I first entered the dungeon.",
       "end": "[BOSS] Thorn: Welcome Adventurers! I am Thorn, the Spirit! And host of the Vegan Trials!",
       "color": 5592575
     },
@@ -280,7 +280,7 @@
     },
     {
       "name": "Run Time",
-      "start": "§e[NPC] §bMort§f: Here, I found this map when I first entered the dungeon.",
+      "start": "[NPC] Mort: Here, I found this map when I first entered the dungeon.",
       "end": "some dummy string",
       "color": 4279733527
     }
@@ -288,7 +288,7 @@
   "M4": [
     {
       "name": "Blood Open",
-      "start": "§e[NPC] §bMort§f: Here, I found this map when I first entered the dungeon.",
+      "start": "[NPC] Mort: Here, I found this map when I first entered the dungeon.",
       "end": "[BOSS] The Watcher: You've managed to scratch and claw your way here, eh?",
       "color": 11141120
     },
@@ -306,7 +306,7 @@
     },
     {
       "name": "Boss Entry",
-      "start": "§e[NPC] §bMort§f: Here, I found this map when I first entered the dungeon.",
+      "start": "[NPC] Mort: Here, I found this map when I first entered the dungeon.",
       "end": "[BOSS] Thorn: Welcome Adventurers! I am Thorn, the Spirit! And host of the Vegan Trials!",
       "color": 5592575
     },
@@ -318,7 +318,7 @@
     },
     {
       "name": "Run Time",
-      "start": "§e[NPC] §bMort§f: Here, I found this map when I first entered the dungeon.",
+      "start": "[NPC] Mort: Here, I found this map when I first entered the dungeon.",
       "end": "some dummy string",
       "color": 4279733527
     }
@@ -326,7 +326,7 @@
   "F5": [
     {
       "name": "Blood Open",
-      "start": "§e[NPC] §bMort§f: Here, I found this map when I first entered the dungeon.",
+      "start": "[NPC] Mort: Here, I found this map when I first entered the dungeon.",
       "end": "[BOSS] The Watcher: I'm starting to get tired of seeing you around here...",
       "color": 11141120
     },
@@ -344,7 +344,7 @@
     },
     {
       "name": "Boss Entry",
-      "start": "§e[NPC] §bMort§f: Here, I found this map when I first entered the dungeon.",
+      "start": "[NPC] Mort: Here, I found this map when I first entered the dungeon.",
       "end": "[BOSS] Livid: Welcome, you've arrived right on time. I am Livid, the Master of Shadows.",
       "color": 5592575
     },
@@ -356,7 +356,7 @@
     },
     {
       "name": "Run Time",
-      "start": "§e[NPC] §bMort§f: Here, I found this map when I first entered the dungeon.",
+      "start": "[NPC] Mort: Here, I found this map when I first entered the dungeon.",
       "end": "some dummy string",
       "color": 4279733527
     }
@@ -364,7 +364,7 @@
   "M5": [
     {
       "name": "Blood Open",
-      "start": "§e[NPC] §bMort§f: Here, I found this map when I first entered the dungeon.",
+      "start": "[NPC] Mort: Here, I found this map when I first entered the dungeon.",
       "end": "[BOSS] The Watcher: I'm starting to get tired of seeing you around here...",
       "color": 11141120
     },
@@ -382,7 +382,7 @@
     },
     {
       "name": "Boss Entry",
-      "start": "§e[NPC] §bMort§f: Here, I found this map when I first entered the dungeon.",
+      "start": "[NPC] Mort: Here, I found this map when I first entered the dungeon.",
       "end": "[BOSS] Livid: Welcome, you've arrived right on time. I am Livid, the Master of Shadows.",
       "color": 5592575
     },
@@ -394,7 +394,7 @@
     },
     {
       "name": "Run Time",
-      "start": "§e[NPC] §bMort§f: Here, I found this map when I first entered the dungeon.",
+      "start": "[NPC] Mort: Here, I found this map when I first entered the dungeon.",
       "end": "some dummy string",
       "color": 4279733527
     }
@@ -402,7 +402,7 @@
   "F6": [
     {
       "name": "Blood Open",
-      "start": "§e[NPC] §bMort§f: Here, I found this map when I first entered the dungeon.",
+      "start": "[NPC] Mort: Here, I found this map when I first entered the dungeon.",
       "end": "[BOSS] The Watcher: Oh.. hello?",
       "color": 11141120
     },
@@ -420,7 +420,7 @@
     },
     {
       "name": "Boss Entry",
-      "start": "§e[NPC] §bMort§f: Here, I found this map when I first entered the dungeon.",
+      "start": "[NPC] Mort: Here, I found this map when I first entered the dungeon.",
       "end": "[BOSS] Sadan: So you made it all the way here... Now you wish to defy me? Sadan?!",
       "color": 5592575
     },
@@ -444,7 +444,7 @@
     },
     {
       "name": "Run Time",
-      "start": "§e[NPC] §bMort§f: Here, I found this map when I first entered the dungeon.",
+      "start": "[NPC] Mort: Here, I found this map when I first entered the dungeon.",
       "end": "some dummy string",
       "color": 4279733527
     }
@@ -452,7 +452,7 @@
   "M6": [
     {
       "name": "Blood Open",
-      "start": "§e[NPC] §bMort§f: Here, I found this map when I first entered the dungeon.",
+      "start": "[NPC] Mort: Here, I found this map when I first entered the dungeon.",
       "end": "[BOSS] The Watcher: Oh.. hello?",
       "color": 11141120
     },
@@ -470,7 +470,7 @@
     },
     {
       "name": "Boss Entry",
-      "start": "§e[NPC] §bMort§f: Here, I found this map when I first entered the dungeon.",
+      "start": "[NPC] Mort: Here, I found this map when I first entered the dungeon.",
       "end": "[BOSS] Sadan: So you made it all the way here... Now you wish to defy me? Sadan?!",
       "color": 5592575
     },
@@ -494,7 +494,7 @@
     },
     {
       "name": "Run Time",
-      "start": "§e[NPC] §bMort§f: Here, I found this map when I first entered the dungeon.",
+      "start": "[NPC] Mort: Here, I found this map when I first entered the dungeon.",
       "end": "some dummy string",
       "color": 4279733527
     }
@@ -502,7 +502,7 @@
   "F7": [
     {
       "name": "Blood Open",
-      "start": "§e[NPC] §bMort§f: Here, I found this map when I first entered the dungeon.",
+      "start": "[NPC] Mort: Here, I found this map when I first entered the dungeon.",
       "end": "[BOSS] The Watcher: Things feel a little more roomy now, eh?",
       "color": 11141120
     },
@@ -520,7 +520,7 @@
     },
     {
       "name": "Boss Entry",
-      "start": "§e[NPC] §bMort§f: Here, I found this map when I first entered the dungeon.",
+      "start": "[NPC] Mort: Here, I found this map when I first entered the dungeon.",
       "end": "[BOSS] Maxor: WELL! WELL! WELL! LOOK WHO'S HERE!",
       "color": 5592575
     },
@@ -556,7 +556,7 @@
     },
     {
       "name": "Run Time",
-      "start": "§e[NPC] §bMort§f: Here, I found this map when I first entered the dungeon.",
+      "start": "[NPC] Mort: Here, I found this map when I first entered the dungeon.",
       "end": "some dummy string",
       "color": 4279733527
     }
@@ -564,7 +564,7 @@
   "M7": [
     {
       "name": "Blood Open",
-      "start": "§e[NPC] §bMort§f: Here, I found this map when I first entered the dungeon.",
+      "start": "[NPC] Mort: Here, I found this map when I first entered the dungeon.",
       "end": "[BOSS] The Watcher: Things feel a little more roomy now, eh?",
       "color": 11141120
     },
@@ -582,7 +582,7 @@
     },
     {
       "name": "Boss Entry",
-      "start": "§e[NPC] §bMort§f: Here, I found this map when I first entered the dungeon.",
+      "start": "[NPC] Mort: Here, I found this map when I first entered the dungeon.",
       "end": "[BOSS] Maxor: WELL! WELL! WELL! LOOK WHO'S HERE!",
       "color": 5592575
     },
@@ -624,7 +624,7 @@
     },
     {
       "name": "Run Time",
-      "start": "§e[NPC] §bMort§f: Here, I found this map when I first entered the dungeon.",
+      "start": "[NPC] Mort: Here, I found this map when I first entered the dungeon.",
       "end": "some dummy string",
       "color": 4279733527
     }
- made rerollblocker on click instead of scanningGabriel Ellnebrand · 2026-09-14 · e6f2e99
查看变更
src/main/java/blade/addon/features/dungeon/RerollBlocker.java         | 81 +++++++++++++++++++++++++++++++++++----------------------------------------------
 src/main/java/blade/addon/features/other/BlockPetMenu.java            |  2 +-
 src/main/java/blade/addon/mixin/AbstractContainerScreenMixin.java     |  2 +-
 src/main/java/blade/addon/utils/events/interfaces/SlotClickEvent.java |  3 ++-
 4 files changed, 39 insertions(+), 49 deletions(-)
diff --git a/src/main/java/blade/addon/features/dungeon/RerollBlocker.java b/src/main/java/blade/addon/features/dungeon/RerollBlocker.java
index 313789f..947cd66 100644
--- a/src/main/java/blade/addon/features/dungeon/RerollBlocker.java
+++ b/src/main/java/blade/addon/features/dungeon/RerollBlocker.java
@@ -6,7 +6,7 @@ import blade.addon.utils.config.values.Dungeons;
 import blade.addon.utils.events.Events;
 import net.minecraft.client.gui.screens.inventory.ContainerScreen;
 import net.minecraft.network.chat.Component;
-import net.minecraft.network.protocol.game.ClientboundContainerSetSlotPacket;
+import net.minecraft.world.Container;
 import net.minecraft.world.inventory.ChestMenu;
 import net.minecraft.world.inventory.ContainerInput;
 import net.minecraft.world.item.ItemStack;
@@ -14,67 +14,38 @@ import net.minecraft.world.item.Items;
 
 public class RerollBlocker {
 
-    //,"Emerald", "Diamond", "Gold", "Wood"
-    private static final String[] CHESTS = {"Bedrock", "Obsidian"};
-    private static final String[] BLOCKED_REROLLS = {"Recombobulator", "Wither Shield", "Implosion", "Shadow Warp", "Necron's Handle", "Star", "Dark Claymore", "Dye", "Giant's Sword", "Shadow fury"};
+    private static final String[] BLOCKED_REROLLS = {"Recombobulator ", "Wither Shield", "Implosion", "Shadow Warp", "Necron's Handle", "Star", "Dark Claymore", "Dye", "Giant's Sword", "Shadow fury"};
 
     private static boolean blockClick = false;
     private static int containerId = Integer.MIN_VALUE;
 
     public static void init() {
 
-        Events.ON_SCREEN.register(screen -> {
-            if (!Dungeons.blockExpensiveRerolls || !Location.in(Location.DUNGEON_HUB)) return false;
-
-            blockClick = false;
+        Events.ON_SCREEN.register(_ -> {
             containerId = Integer.MIN_VALUE;
-
-            if (screen instanceof ContainerScreen containerScreen) {
-
-                Component title = containerScreen.getTitle();
-                if (title == null) return false;
-
-                if (!isValidGUI(title.getString())) return false;
-
-                ChestMenu menu = containerScreen.getMenu();
-                containerId = menu.containerId;
-            }
             return false;
         });
 
-        Events.ON_PACKET.register(packet -> {
+        Events.ON_SLOT_CLICKED.register((slot, slotId, _, containerInput, screen) -> {
             if (!Dungeons.blockExpensiveRerolls || !Location.in(Location.DUNGEON_HUB)) return false;
+            if (containerInput == ContainerInput.THROW) return false;
+            if (slotId != 50 || slot.getItem().getItem() != Items.FEATHER) return false;
 
-            if (blockClick) return false;
-            if (packet instanceof ClientboundContainerSetSlotPacket containerSetSlotPacket) {
-                if (containerSetSlotPacket.getContainerId() != containerId) return false;
-                if (containerSetSlotPacket.getSlot() >= 54) return false;
-
-                ItemStack itemStack = containerSetSlotPacket.getItem();
-                Component component = itemStack.getCustomName();
-                if (component == null) return false;
+            if (screen instanceof ContainerScreen containerScreen) {
+                ChestMenu menu = containerScreen.getMenu();
+                if (menu == null) return false;
+                Container container = menu.getContainer();
+                if (container == null) return false;
 
-                String blocked = getBlockedItem(component.getString());
-                if (blocked != null) {
-                    blockClick = true;
-                    Misc.addChatMessage(Component.literal("Blocked reroll {name: " + blocked + "} {Item name: " + component.getString() + "}"));
+                if (menu.containerId != containerId) {
+                    scanContainer(container);
+                    containerId = menu.containerId;
                 }
-
             }
 
-            return false;
-        });
-
-        Events.ON_SLOT_CLICKED.register((slot, slotId, _, containerInput) -> {
-            Misc.addChatMessage(Component.literal(slotId + " " + containerInput));
-            if (!Dungeons.blockExpensiveRerolls || !Location.in(Location.DUNGEON_HUB)) return false;
-            if (containerInput == ContainerInput.THROW) return false;
-            Misc.addChatMessage(Component.literal(slot.getItem().getItem().toString()));
-            if (slotId != 50 || slot.getItem().getItem() != Items.FEATHER) return false;
             ItemStack itemStack = slot.getItem();
             Component customName = itemStack.getCustomName();
             if (customName == null) return false;
-            Misc.addChatMessage(customName);
             if (!customName.getString().contains("Reroll Chest")) return false;
 
             return blockClick;
@@ -82,10 +53,28 @@ public class RerollBlocker {
 
     }
 
-    private static boolean isValidGUI(String title) {
-        for (String name : CHESTS) {
-            if (name.equalsIgnoreCase(title)) return true;
+    private static void scanContainer(Container container) {
+        for (int i = 0; i < 54; i++) {
+           ItemStack itemStack = container.getItem(i);
+           if (scanItem(itemStack)) {
+               blockClick = true;
+               return;
+           }
         }
+
+        blockClick = false;
+    }
+
+    private static boolean scanItem(ItemStack itemStack) {
+        Component component = itemStack.getCustomName();
+        if (component == null) return false;
+
+        String blocked = getBlockedItem(component.getString());
+        if (blocked != null) {
+            Misc.addChatMessage(Component.literal("Blocked reroll {name: " + blocked + "} {Item name: " + component.getString() + "}"));
+            return true;
+        }
+
         return false;
     }
 
diff --git a/src/main/java/blade/addon/features/other/BlockPetMenu.java b/src/main/java/blade/addon/features/other/BlockPetMenu.java
index a6e02de..ab2b46e 100644
--- a/src/main/java/blade/addon/features/other/BlockPetMenu.java
+++ b/src/main/java/blade/addon/features/other/BlockPetMenu.java
@@ -19,7 +19,7 @@ public class BlockPetMenu {
             return false;
         });
 
-        Events.ON_SLOT_CLICKED.register((slot,slotId, button, containerInput) -> {
+        Events.ON_SLOT_CLICKED.register((_,slotId, button, _, _) -> {
             if (inPetMenu && button == 1 && Dungeons.blockRemovingPet && Location.inDungeon()) {
                 return slotId >= 9 && slotId <= 44;
             }
diff --git a/src/main/java/blade/addon/mixin/AbstractContainerScreenMixin.java b/src/main/java/blade/addon/mixin/AbstractContainerScreenMixin.java
index f22612d..ce5db12 100644
--- a/src/main/java/blade/addon/mixin/AbstractContainerScreenMixin.java
+++ b/src/main/java/blade/addon/mixin/AbstractContainerScreenMixin.java
@@ -95,7 +95,7 @@ public abstract class AbstractContainerScreenMixin<T extends AbstractContainerMe
 
     @Inject(method = "slotClicked", at = @At("HEAD"), cancellable = true)
     public void slotClicked(Slot slot, int slotId, int buttonNum, ContainerInput containerInput, CallbackInfo ci) {
-        if (Events.ON_SLOT_CLICKED.invoke(slotEvent -> slotEvent.onSlot(slot, slotId, buttonNum, containerInput))) {
+        if (Events.ON_SLOT_CLICKED.invoke(slotEvent -> slotEvent.onSlot(slot, slotId, buttonNum, containerInput, this))) {
             ci.cancel();
         }
     }
diff --git a/src/main/java/blade/addon/utils/events/interfaces/SlotClickEvent.java b/src/main/java/blade/addon/utils/events/interfaces/SlotClickEvent.java
index a4de6f8..528226c 100644
--- a/src/main/java/blade/addon/utils/events/interfaces/SlotClickEvent.java
+++ b/src/main/java/blade/addon/utils/events/interfaces/SlotClickEvent.java
@@ -1,8 +1,9 @@
 package blade.addon.utils.events.interfaces;
 
+import net.minecraft.client.gui.screens.Screen;
 import net.minecraft.world.inventory.ContainerInput;
 import net.minecraft.world.inventory.Slot;
 
 public interface SlotClickEvent {
-    boolean onSlot(Slot slot, int slotId, int button, ContainerInput containerInput);
+    boolean onSlot(Slot slot, int slotId, int button, ContainerInput containerInput, Screen container);
 }
- added a bunch of debug messages tempGabriel Ellnebrand · 2026-09-11 · fceac7f
查看变更
src/main/java/blade/addon/features/dungeon/RerollBlocker.java | 7 ++++---
 1 file changed, 4 insertions(+), 3 deletions(-)
diff --git a/src/main/java/blade/addon/features/dungeon/RerollBlocker.java b/src/main/java/blade/addon/features/dungeon/RerollBlocker.java
index 91e7f0f..313789f 100644
--- a/src/main/java/blade/addon/features/dungeon/RerollBlocker.java
+++ b/src/main/java/blade/addon/features/dungeon/RerollBlocker.java
@@ -16,7 +16,7 @@ public class RerollBlocker {
 
     //,"Emerald", "Diamond", "Gold", "Wood"
     private static final String[] CHESTS = {"Bedrock", "Obsidian"};
-    private static final String[] BLOCKED_REROLLS = {"Recombobulator ", "Wither Shield", "Implosion", "Shadow Warp", "Necron's Handle", "Star", "Dark Claymore", "Dye", "Giant's Sword", "Shadow fury"};
+    private static final String[] BLOCKED_REROLLS = {"Recombobulator", "Wither Shield", "Implosion", "Shadow Warp", "Necron's Handle", "Star", "Dark Claymore", "Dye", "Giant's Sword", "Shadow fury"};
 
     private static boolean blockClick = false;
     private static int containerId = Integer.MIN_VALUE;
@@ -66,14 +66,15 @@ public class RerollBlocker {
         });
 
         Events.ON_SLOT_CLICKED.register((slot, slotId, _, containerInput) -> {
+            Misc.addChatMessage(Component.literal(slotId + " " + containerInput));
             if (!Dungeons.blockExpensiveRerolls || !Location.in(Location.DUNGEON_HUB)) return false;
             if (containerInput == ContainerInput.THROW) return false;
+            Misc.addChatMessage(Component.literal(slot.getItem().getItem().toString()));
             if (slotId != 50 || slot.getItem().getItem() != Items.FEATHER) return false;
-
             ItemStack itemStack = slot.getItem();
             Component customName = itemStack.getCustomName();
             if (customName == null) return false;
-
+            Misc.addChatMessage(customName);
             if (!customName.getString().contains("Reroll Chest")) return false;
 
             return blockClick;
- added a space so it hopefully skips auto recombsGabriel Ellnebrand · 2026-09-11 · 4f36b31
查看变更
src/main/java/blade/addon/features/dungeon/RerollBlocker.java | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/main/java/blade/addon/features/dungeon/RerollBlocker.java b/src/main/java/blade/addon/features/dungeon/RerollBlocker.java
index 7d7c3c3..91e7f0f 100644
--- a/src/main/java/blade/addon/features/dungeon/RerollBlocker.java
+++ b/src/main/java/blade/addon/features/dungeon/RerollBlocker.java
@@ -16,7 +16,7 @@ public class RerollBlocker {
 
     //,"Emerald", "Diamond", "Gold", "Wood"
     private static final String[] CHESTS = {"Bedrock", "Obsidian"};
-    private static final String[] BLOCKED_REROLLS = {"Recombobulator", "Wither Shield", "Implosion", "Shadow Warp", "Necron's Handle", "Star", "Dark Claymore", "Dye", "Giant's Sword", "Shadow fury"};
+    private static final String[] BLOCKED_REROLLS = {"Recombobulator ", "Wither Shield", "Implosion", "Shadow Warp", "Necron's Handle", "Star", "Dark Claymore", "Dye", "Giant's Sword", "Shadow fury"};
 
     private static boolean blockClick = false;
     private static int containerId = Integer.MIN_VALUE;
- testing if removing number stops stupid shitGabriel Ellnebrand · 2026-09-11 · 28277aa
查看变更
src/main/java/blade/addon/features/dungeon/RerollBlocker.java | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/main/java/blade/addon/features/dungeon/RerollBlocker.java b/src/main/java/blade/addon/features/dungeon/RerollBlocker.java
index 46f58b7..7d7c3c3 100644
--- a/src/main/java/blade/addon/features/dungeon/RerollBlocker.java
+++ b/src/main/java/blade/addon/features/dungeon/RerollBlocker.java
@@ -16,7 +16,7 @@ public class RerollBlocker {
 
     //,"Emerald", "Diamond", "Gold", "Wood"
     private static final String[] CHESTS = {"Bedrock", "Obsidian"};
-    private static final String[] BLOCKED_REROLLS = {"Recombobulator 3000", "Wither Shield", "Implosion", "Shadow Warp", "Necron's Handle", "Star", "Dark Claymore", "Dye", "Giant's Sword", "Shadow fury"};
+    private static final String[] BLOCKED_REROLLS = {"Recombobulator", "Wither Shield", "Implosion", "Shadow Warp", "Necron's Handle", "Star", "Dark Claymore", "Dye", "Giant's Sword", "Shadow fury"};
 
     private static boolean blockClick = false;
     private static int containerId = Integer.MIN_VALUE;
- changed override for blockedreroll to the drop key - fixed it checking inventory for blocked itemsGabriel Ellnebrand · 2026-09-11 · 37a6bea
查看变更
src/main/java/blade/addon/features/dungeon/RerollBlocker.java  | 5 +++--
 src/main/java/blade/addon/mixin/GuiGraphicsExtractorMixin.java | 1 +
 2 files changed, 4 insertions(+), 2 deletions(-)
diff --git a/src/main/java/blade/addon/features/dungeon/RerollBlocker.java b/src/main/java/blade/addon/features/dungeon/RerollBlocker.java
index 17032e6..46f58b7 100644
--- a/src/main/java/blade/addon/features/dungeon/RerollBlocker.java
+++ b/src/main/java/blade/addon/features/dungeon/RerollBlocker.java
@@ -16,7 +16,7 @@ public class RerollBlocker {
 
     //,"Emerald", "Diamond", "Gold", "Wood"
     private static final String[] CHESTS = {"Bedrock", "Obsidian"};
-    private static final String[] BLOCKED_REROLLS = {"Wither Shield", "Implosion", "Shadow Warp", "Necron's Handle", "Star", "Dark Claymore", "Dye", "Giant's Sword", "Shadow fury"};
+    private static final String[] BLOCKED_REROLLS = {"Recombobulator 3000", "Wither Shield", "Implosion", "Shadow Warp", "Necron's Handle", "Star", "Dark Claymore", "Dye", "Giant's Sword", "Shadow fury"};
 
     private static boolean blockClick = false;
     private static int containerId = Integer.MIN_VALUE;
@@ -48,6 +48,7 @@ public class RerollBlocker {
             if (blockClick) return false;
             if (packet instanceof ClientboundContainerSetSlotPacket containerSetSlotPacket) {
                 if (containerSetSlotPacket.getContainerId() != containerId) return false;
+                if (containerSetSlotPacket.getSlot() >= 54) return false;
 
                 ItemStack itemStack = containerSetSlotPacket.getItem();
                 Component component = itemStack.getCustomName();
@@ -66,7 +67,7 @@ public class RerollBlocker {
 
         Events.ON_SLOT_CLICKED.register((slot, slotId, _, containerInput) -> {
             if (!Dungeons.blockExpensiveRerolls || !Location.in(Location.DUNGEON_HUB)) return false;
-            if (containerInput == ContainerInput.QUICK_MOVE) return false;
+            if (containerInput == ContainerInput.THROW) return false;
             if (slotId != 50 || slot.getItem().getItem() != Items.FEATHER) return false;
 
             ItemStack itemStack = slot.getItem();
diff --git a/src/main/java/blade/addon/mixin/GuiGraphicsExtractorMixin.java b/src/main/java/blade/addon/mixin/GuiGraphicsExtractorMixin.java
index 291e890..1a6bda3 100644
--- a/src/main/java/blade/addon/mixin/GuiGraphicsExtractorMixin.java
+++ b/src/main/java/blade/addon/mixin/GuiGraphicsExtractorMixin.java
@@ -76,6 +76,7 @@ public abstract class GuiGraphicsExtractorMixin {
         float newX = xo + 6;
         float newY = yo - ((float) h / 2) * Visual.tooltipSize + 4;
 
+        //remove bottom overflow hopefully
         if ((h + newY) * Visual.tooltipSize > this.guiHeight()) {
             newY = this.guiHeight() - h - 4;
         }
- reworked tooltip scalarGabriel Ellnebrand · 2026-09-11 · dfe74bd
查看变更
src/main/java/blade/addon/mixin/GuiGraphicsExtractorMixin.java | 49 ++++++++++++++++++++++++++++++-------------------
 src/main/java/blade/addon/utils/config/Config.java             |  2 +-
 2 files changed, 31 insertions(+), 20 deletions(-)
diff --git a/src/main/java/blade/addon/mixin/GuiGraphicsExtractorMixin.java b/src/main/java/blade/addon/mixin/GuiGraphicsExtractorMixin.java
index e871df0..291e890 100644
--- a/src/main/java/blade/addon/mixin/GuiGraphicsExtractorMixin.java
+++ b/src/main/java/blade/addon/mixin/GuiGraphicsExtractorMixin.java
@@ -1,6 +1,8 @@
 package blade.addon.mixin;
 
 import blade.addon.utils.config.values.Visual;
+import com.llamalad7.mixinextras.injector.ModifyExpressionValue;
+import com.llamalad7.mixinextras.sugar.Local;
 import net.minecraft.client.gui.Font;
 import net.minecraft.client.gui.GuiGraphicsExtractor;
 import net.minecraft.client.gui.screens.inventory.tooltip.ClientTooltipComponent;
@@ -16,20 +18,21 @@ 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.ModifyArgs;
 import org.spongepowered.asm.mixin.injection.ModifyVariable;
 import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
-import org.spongepowered.asm.mixin.injection.invoke.arg.Args;
 
 import java.util.List;
 
 @Mixin(GuiGraphicsExtractor.class)
-public class GuiGraphicsExtractorMixin {
+public abstract class GuiGraphicsExtractorMixin {
 
     @Final
     @Shadow
     private Matrix3x2fStack pose;
 
+    @Shadow
+    public abstract int guiHeight();
+
     @ModifyVariable(method = "itemCooldown", at = @At("STORE"), ordinal = 0)
     private float noCooldown(float f) {
         return Visual.hideCooldown ? 0 : f;
@@ -54,27 +57,35 @@ public class GuiGraphicsExtractorMixin {
         }
     }
 
-    @Inject(method = "tooltip", at = @At(value = "INVOKE", target = "Lorg/joml/Matrix3x2fStack;pushMatrix()Lorg/joml/Matrix3x2fStack;"))
-    private void scaleUpTooltip_head(Font font, List<ClientTooltipComponent> lines, int xo, int yo, ClientTooltipPositioner positioner, @Nullable Identifier style, CallbackInfo ci) {
-
-        float scaledX = xo - (float) xo * Visual.tooltipSize;
-        float scaledY = yo - (float) yo * Visual.tooltipSize;
-
-        pose.translate(scaledX, scaledY);
-        pose.scale(Visual.tooltipSize);
+    @ModifyExpressionValue(method = "tooltip", at = @At(value = "INVOKE", target = "Lorg/joml/Vector2ic;x()I"))
+    private int modifyTooltipX(int x) {
+        if (Visual.tooltipSize == 1) return x;
+        return 0;
     }
 
-    @ModifyArgs(method = "tooltip", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/gui/screens/inventory/tooltip/ClientTooltipPositioner;positionTooltip(IIIIII)Lorg/joml/Vector2ic;"))
-    private void scaleUpTooltip(Args args) {
+    @ModifyExpressionValue(method = "tooltip", at = @At(value = "INVOKE", target = "Lorg/joml/Vector2ic;y()I"))
+    private int modifyTooltipY(int y) {
+        if (Visual.tooltipSize == 1) return y;
+        return 0;
+    }
 
-        int w = args.get(4);
-        int h = args.get(5);
+    @Inject(method = "tooltip", at = @At(value = "INVOKE", target = "Lorg/joml/Matrix3x2fStack;pushMatrix()Lorg/joml/Matrix3x2fStack;"))
+    private void scaleUpTooltip_head(Font font, List<ClientTooltipComponent> lines, int xo, int yo, ClientTooltipPositioner positioner,
+                                     @Nullable Identifier style, CallbackInfo ci, @Local(name = "h") int h) {
+        if (Visual.tooltipSize == 1) return;
+        float newX = xo + 6;
+        float newY = yo - ((float) h / 2) * Visual.tooltipSize + 4;
+
+        if ((h + newY) * Visual.tooltipSize > this.guiHeight()) {
+            newY = this.guiHeight() - h - 4;
+        }
 
-        float scaledW = (float) w * Visual.tooltipSize;
-        float scaledH = (float) h * Visual.tooltipSize;
+        //remove top and left overflow
+        if (newX < 0) newX = 6;
+        if (newY < 0) newY = 4;
 
-        args.set(4, (int) scaledW);
-        args.set(5, (int) scaledH);
+        pose.translate(newX, newY);
+        pose.scale(Visual.tooltipSize);
     }
 
 }
diff --git a/src/main/java/blade/addon/utils/config/Config.java b/src/main/java/blade/addon/utils/config/Config.java
index d61ab6c..4d2c08f 100644
--- a/src/main/java/blade/addon/utils/config/Config.java
+++ b/src/main/java/blade/addon/utils/config/Config.java
@@ -424,7 +424,7 @@ public class Config {
         ConfigSection toolTip = new ConfigSection(Component.literal("ToolTips"));
         toolTip.add(new ConfigBool(Component.literal("Item quality"), () -> Visual.itemQuality, bool -> Visual.itemQuality = bool));
         toolTip.add(new ConfigBool(Component.literal("Pet candy"), () -> Visual.petCandy, bool -> Visual.petCandy = bool));
-        toolTip.add(new ConfigFloat(Component.literal("Tooltip size"), () -> Visual.tooltipSize, num -> Visual.tooltipSize = num, 0.01f, 0.25f, 2));
+        toolTip.add(new ConfigFloat(Component.literal("Tooltip size"), () -> Visual.tooltipSize, num -> Visual.tooltipSize = num, 0.01f, 0.25f, 1));
         visual.add(toolTip);
 
         visual.add(new ConfigBool(Component.literal("Hide non crits"), () -> Visual.hideNoneCrits, bool -> Visual.hideNoneCrits = bool));
- made tooltipSize's range be bigger and allowed more precision - hopefully fixed rerollblocker having reversed logic - made tooltip scale not make the text fucked upGabriel Ellnebrand · 2026-09-11 · 403553f
查看变更
src/main/java/blade/addon/features/dungeon/RerollBlocker.java  |  4 ++--
 src/main/java/blade/addon/mixin/GuiGraphicsExtractorMixin.java | 45 ++++++++++++++++++---------------------------
 src/main/java/blade/addon/utils/config/Config.java             |  2 +-
 3 files changed, 21 insertions(+), 30 deletions(-)
diff --git a/src/main/java/blade/addon/features/dungeon/RerollBlocker.java b/src/main/java/blade/addon/features/dungeon/RerollBlocker.java
index 3a1ce34..17032e6 100644
--- a/src/main/java/blade/addon/features/dungeon/RerollBlocker.java
+++ b/src/main/java/blade/addon/features/dungeon/RerollBlocker.java
@@ -16,7 +16,7 @@ public class RerollBlocker {
 
     //,"Emerald", "Diamond", "Gold", "Wood"
     private static final String[] CHESTS = {"Bedrock", "Obsidian"};
-    private static final String[] BLOCKED_REROLLS = {"Wither Shield", "Implosion", "Shadow Warp", "Necron's Handle", "Star", "Claymore"};
+    private static final String[] BLOCKED_REROLLS = {"Wither Shield", "Implosion", "Shadow Warp", "Necron's Handle", "Star", "Dark Claymore", "Dye", "Giant's Sword", "Shadow fury"};
 
     private static boolean blockClick = false;
     private static int containerId = Integer.MIN_VALUE;
@@ -89,7 +89,7 @@ public class RerollBlocker {
 
     private static String getBlockedItem(String itemName) {
         for (String name : BLOCKED_REROLLS) {
-            if (name.contains(itemName)) return name;
+            if (itemName.contains(name)) return name;
         }
         return null;
     }
diff --git a/src/main/java/blade/addon/mixin/GuiGraphicsExtractorMixin.java b/src/main/java/blade/addon/mixin/GuiGraphicsExtractorMixin.java
index dd8db59..e871df0 100644
--- a/src/main/java/blade/addon/mixin/GuiGraphicsExtractorMixin.java
+++ b/src/main/java/blade/addon/mixin/GuiGraphicsExtractorMixin.java
@@ -29,17 +29,17 @@ public class GuiGraphicsExtractorMixin {
     @Final
     @Shadow
     private Matrix3x2fStack pose;
-    
-    @ModifyVariable(method = "itemCooldown", at=@At("STORE"), ordinal = 0)
+
+    @ModifyVariable(method = "itemCooldown", at = @At("STORE"), ordinal = 0)
     private float noCooldown(float f) {
-        return Visual.hideCooldown? 0: f;
+        return Visual.hideCooldown ? 0 : f;
     }
 
-    @Inject(method = "item(Lnet/minecraft/world/entity/LivingEntity;Lnet/minecraft/world/item/ItemStack;III)V", at=@At("HEAD"))
+    @Inject(method = "item(Lnet/minecraft/world/entity/LivingEntity;Lnet/minecraft/world/item/ItemStack;III)V", at = @At("HEAD"))
     private void scaleUp(LivingEntity owner, ItemStack stack, int x, int y, int seed, CallbackInfo ci) {
         if (Visual.oldPlayerHead && stack.getItem() == Items.PLAYER_HEAD) {
             float scale = 0.875f;
-            int offset = (16 - (int)(scale * 16)) / 2;
+            int offset = (16 - (int) (scale * 16)) / 2;
 
             pose.pushMatrix();
             pose.translate(x * (1 - scale) + offset, y * (1 - scale) + offset);
@@ -47,43 +47,34 @@ public class GuiGraphicsExtractorMixin {
         }
     }
 
-    @Inject(method = "item(Lnet/minecraft/world/entity/LivingEntity;Lnet/minecraft/world/item/ItemStack;III)V", at=@At("TAIL"))
+    @Inject(method = "item(Lnet/minecraft/world/entity/LivingEntity;Lnet/minecraft/world/item/ItemStack;III)V", at = @At("TAIL"))
     private void scaleDown(LivingEntity owner, ItemStack stack, int x, int y, int seed, CallbackInfo ci) {
         if (Visual.oldPlayerHead && stack.getItem() == Items.PLAYER_HEAD) {
             pose.popMatrix();
         }
     }
 
-    @Inject(method = "tooltip", at= @At(value = "INVOKE", target = "Lorg/joml/Matrix3x2fStack;pushMatrix()Lorg/joml/Matrix3x2fStack;"))
+    @Inject(method = "tooltip", at = @At(value = "INVOKE", target = "Lorg/joml/Matrix3x2fStack;pushMatrix()Lorg/joml/Matrix3x2fStack;"))
     private void scaleUpTooltip_head(Font font, List<ClientTooltipComponent> lines, int xo, int yo, ClientTooltipPositioner positioner, @Nullable Identifier style, CallbackInfo ci) {
+
+        float scaledX = xo - (float) xo * Visual.tooltipSize;
+        float scaledY = yo - (float) yo * Visual.tooltipSize;
+
+        pose.translate(scaledX, scaledY);
         pose.scale(Visual.tooltipSize);
     }
 
-    @ModifyArgs(method = "tooltip", at= @At(value = "INVOKE", target = "Lnet/minecraft/client/gui/screens/inventory/tooltip/ClientTooltipPositioner;positionTooltip(IIIIII)Lorg/joml/Vector2ic;"))
+    @ModifyArgs(method = "tooltip", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/gui/screens/inventory/tooltip/ClientTooltipPositioner;positionTooltip(IIIIII)Lorg/joml/Vector2ic;"))
     private void scaleUpTooltip(Args args) {
 
-        //Yeah, this is a bad way to do this, but I'm lazy
-
-        int sw = args.get(0);
-        int sh = args.get(1);
-        int x = args.get(2);
-        int y = args.get(3);
         int w = args.get(4);
         int h = args.get(5);
 
-        float scaledSW = (float)sw / Visual.tooltipSize;
-        float scaledSH = (float)sh / Visual.tooltipSize;
-        float scaledX = (float)x / Visual.tooltipSize;
-        float scaledY = (float)y / Visual.tooltipSize;
-        float scaledW = (float)w / Visual.tooltipSize;
-        float scaledH = (float)h / Visual.tooltipSize;
-
-        args.set(0, (int)scaledSW);
-        args.set(1, (int)scaledSH);
-        args.set(2, (int)scaledX);
-        args.set(3, (int)scaledY);
-        args.set(4, (int)scaledW);
-        args.set(5, (int)scaledH);
+        float scaledW = (float) w * Visual.tooltipSize;
+        float scaledH = (float) h * Visual.tooltipSize;
+
+        args.set(4, (int) scaledW);
+        args.set(5, (int) scaledH);
     }
 
 }
diff --git a/src/main/java/blade/addon/utils/config/Config.java b/src/main/java/blade/addon/utils/config/Config.java
index 5552dd7..d61ab6c 100644
--- a/src/main/java/blade/addon/utils/config/Config.java
+++ b/src/main/java/blade/addon/utils/config/Config.java
@@ -424,7 +424,7 @@ public class Config {
         ConfigSection toolTip = new ConfigSection(Component.literal("ToolTips"));
         toolTip.add(new ConfigBool(Component.literal("Item quality"), () -> Visual.itemQuality, bool -> Visual.itemQuality = bool));
         toolTip.add(new ConfigBool(Component.literal("Pet candy"), () -> Visual.petCandy, bool -> Visual.petCandy = bool));
-        toolTip.add(new ConfigFloat(Component.literal("Tooltip size"), () -> Visual.tooltipSize, num -> Visual.tooltipSize = num, 0.1f, 0.5f, 2));
+        toolTip.add(new ConfigFloat(Component.literal("Tooltip size"), () -> Visual.tooltipSize, num -> Visual.tooltipSize = num, 0.01f, 0.25f, 2));
         visual.add(toolTip);
 
         visual.add(new ConfigBool(Component.literal("Hide non crits"), () -> Visual.hideNoneCrits, bool -> Visual.hideNoneCrits = bool));
- added temp debug message and added some blocked itemsGabriel Ellnebrand · 2026-09-10 · c1717c1
查看变更
src/main/java/blade/addon/features/dungeon/RerollBlocker.java | 21 +++++++++++++++------
 1 file changed, 15 insertions(+), 6 deletions(-)
diff --git a/src/main/java/blade/addon/features/dungeon/RerollBlocker.java b/src/main/java/blade/addon/features/dungeon/RerollBlocker.java
index fe1002a..3a1ce34 100644
--- a/src/main/java/blade/addon/features/dungeon/RerollBlocker.java
+++ b/src/main/java/blade/addon/features/dungeon/RerollBlocker.java
@@ -1,19 +1,22 @@
 package blade.addon.features.dungeon;
 
 import blade.addon.utils.Location;
+import blade.addon.utils.Misc;
 import blade.addon.utils.config.values.Dungeons;
 import blade.addon.utils.events.Events;
 import net.minecraft.client.gui.screens.inventory.ContainerScreen;
 import net.minecraft.network.chat.Component;
 import net.minecraft.network.protocol.game.ClientboundContainerSetSlotPacket;
 import net.minecraft.world.inventory.ChestMenu;
+import net.minecraft.world.inventory.ContainerInput;
 import net.minecraft.world.item.ItemStack;
 import net.minecraft.world.item.Items;
 
 public class RerollBlocker {
 
+    //,"Emerald", "Diamond", "Gold", "Wood"
     private static final String[] CHESTS = {"Bedrock", "Obsidian"};
-    private static final String[] BLOCKED_REROLLS = {"Wither Shield", "Implosion", "Shadow Warp", "Necron's Handle", "Wither Helmet"};
+    private static final String[] BLOCKED_REROLLS = {"Wither Shield", "Implosion", "Shadow Warp", "Necron's Handle", "Star", "Claymore"};
 
     private static boolean blockClick = false;
     private static int containerId = Integer.MIN_VALUE;
@@ -50,14 +53,20 @@ public class RerollBlocker {
                 Component component = itemStack.getCustomName();
                 if (component == null) return false;
 
-                if (isBlockedReroll(component.getString())) blockClick = true;
+                String blocked = getBlockedItem(component.getString());
+                if (blocked != null) {
+                    blockClick = true;
+                    Misc.addChatMessage(Component.literal("Blocked reroll {name: " + blocked + "} {Item name: " + component.getString() + "}"));
+                }
+
             }
 
             return false;
         });
 
-        Events.ON_SLOT_CLICKED.register((slot, slotId, _, _) -> {
+        Events.ON_SLOT_CLICKED.register((slot, slotId, _, containerInput) -> {
             if (!Dungeons.blockExpensiveRerolls || !Location.in(Location.DUNGEON_HUB)) return false;
+            if (containerInput == ContainerInput.QUICK_MOVE) return false;
             if (slotId != 50 || slot.getItem().getItem() != Items.FEATHER) return false;
 
             ItemStack itemStack = slot.getItem();
@@ -78,11 +87,11 @@ public class RerollBlocker {
         return false;
     }
 
-    private static boolean isBlockedReroll(String itemName) {
+    private static String getBlockedItem(String itemName) {
         for (String name : BLOCKED_REROLLS) {
-            if (name.contains(itemName)) return true;
+            if (name.contains(itemName)) return name;
         }
-        return false;
+        return null;
     }
 
 }
- added block expensive rerollsGabriel Ellnebrand · 2026-09-06 · ae78973
查看变更
src/main/java/blade/addon/Bladeaddons.java                    |  1 +
 src/main/java/blade/addon/features/dungeon/RerollBlocker.java | 88 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 src/main/java/blade/addon/utils/config/Config.java            |  1 +
 src/main/java/blade/addon/utils/config/values/Dungeons.java   |  3 +++
 4 files changed, 93 insertions(+)
diff --git a/src/main/java/blade/addon/Bladeaddons.java b/src/main/java/blade/addon/Bladeaddons.java
index cbbfafc..f9be71c 100644
--- a/src/main/java/blade/addon/Bladeaddons.java
+++ b/src/main/java/blade/addon/Bladeaddons.java
@@ -112,5 +112,6 @@ public class Bladeaddons implements ModInitializer {
         ToolTip.init();
         CompactDamageNumbers.init();
         LoadoutData.init();
+        RerollBlocker.init();
     }
 }
\ No newline at end of file
diff --git a/src/main/java/blade/addon/features/dungeon/RerollBlocker.java b/src/main/java/blade/addon/features/dungeon/RerollBlocker.java
new file mode 100644
index 0000000..fe1002a
--- /dev/null
+++ b/src/main/java/blade/addon/features/dungeon/RerollBlocker.java
@@ -0,0 +1,88 @@
+package blade.addon.features.dungeon;
+
+import blade.addon.utils.Location;
+import blade.addon.utils.config.values.Dungeons;
+import blade.addon.utils.events.Events;
+import net.minecraft.client.gui.screens.inventory.ContainerScreen;
+import net.minecraft.network.chat.Component;
+import net.minecraft.network.protocol.game.ClientboundContainerSetSlotPacket;
+import net.minecraft.world.inventory.ChestMenu;
+import net.minecraft.world.item.ItemStack;
+import net.minecraft.world.item.Items;
+
+public class RerollBlocker {
+
+    private static final String[] CHESTS = {"Bedrock", "Obsidian"};
+    private static final String[] BLOCKED_REROLLS = {"Wither Shield", "Implosion", "Shadow Warp", "Necron's Handle", "Wither Helmet"};
+
+    private static boolean blockClick = false;
+    private static int containerId = Integer.MIN_VALUE;
+
+    public static void init() {
+
+        Events.ON_SCREEN.register(screen -> {
+            if (!Dungeons.blockExpensiveRerolls || !Location.in(Location.DUNGEON_HUB)) return false;
+
+            blockClick = false;
+            containerId = Integer.MIN_VALUE;
+
+            if (screen instanceof ContainerScreen containerScreen) {
+
+                Component title = containerScreen.getTitle();
+                if (title == null) return false;
+
+                if (!isValidGUI(title.getString())) return false;
+
+                ChestMenu menu = containerScreen.getMenu();
+                containerId = menu.containerId;
+            }
+            return false;
+        });
+
+        Events.ON_PACKET.register(packet -> {
+            if (!Dungeons.blockExpensiveRerolls || !Location.in(Location.DUNGEON_HUB)) return false;
+
+            if (blockClick) return false;
+            if (packet instanceof ClientboundContainerSetSlotPacket containerSetSlotPacket) {
+                if (containerSetSlotPacket.getContainerId() != containerId) return false;
+
+                ItemStack itemStack = containerSetSlotPacket.getItem();
+                Component component = itemStack.getCustomName();
+                if (component == null) return false;
+
+                if (isBlockedReroll(component.getString())) blockClick = true;
+            }
+
+            return false;
+        });
+
+        Events.ON_SLOT_CLICKED.register((slot, slotId, _, _) -> {
+            if (!Dungeons.blockExpensiveRerolls || !Location.in(Location.DUNGEON_HUB)) return false;
+            if (slotId != 50 || slot.getItem().getItem() != Items.FEATHER) return false;
+
+            ItemStack itemStack = slot.getItem();
+            Component customName = itemStack.getCustomName();
+            if (customName == null) return false;
+
+            if (!customName.getString().contains("Reroll Chest")) return false;
+
+            return blockClick;
+        });
+
+    }
+
+    private static boolean isValidGUI(String title) {
+        for (String name : CHESTS) {
+            if (name.equalsIgnoreCase(title)) return true;
+        }
+        return false;
+    }
+
+    private static boolean isBlockedReroll(String itemName) {
+        for (String name : BLOCKED_REROLLS) {
+            if (name.contains(itemName)) return true;
+        }
+        return false;
+    }
+
+}
diff --git a/src/main/java/blade/addon/utils/config/Config.java b/src/main/java/blade/addon/utils/config/Config.java
index db674fc..5552dd7 100644
--- a/src/main/java/blade/addon/utils/config/Config.java
+++ b/src/main/java/blade/addon/utils/config/Config.java
@@ -119,6 +119,7 @@ public class Config {
         chests.add(new ConfigBool(Component.literal("Only display after run is over"), () -> Dungeons.onlyAfterRunOver, bool -> Dungeons.onlyAfterRunOver = bool));
         chests.add(new ConfigBool(Component.literal("Send chest count warning"), () -> Dungeons.sendChestWarning, bool -> Dungeons.sendChestWarning = bool));
         chests.add(new ConfigInt(Component.literal("Warning at chest"), () -> Dungeons.chestWarningCount, num -> Dungeons.chestWarningCount = num, 1, 1, 60));
+        chests.add(new ConfigBool(Component.literal("Block expensive rerolls"), () -> Dungeons.blockExpensiveRerolls, bool -> Dungeons.blockExpensiveRerolls = bool));
 
         dungeons.add(chests);
         dungeons.add(new ConfigBool(Component.literal("Leap message"), () -> Dungeons.enableLeapMessages, bool -> Dungeons.enableLeapMessages = bool));
diff --git a/src/main/java/blade/addon/utils/config/values/Dungeons.java b/src/main/java/blade/addon/utils/config/values/Dungeons.java
index 30015ce..b0474e5 100644
--- a/src/main/java/blade/addon/utils/config/values/Dungeons.java
+++ b/src/main/java/blade/addon/utils/config/values/Dungeons.java
@@ -168,4 +168,7 @@ public class Dungeons {
 
     @ConfigValue
     public static SoundData invincibilitySound =  new SoundData(SoundEvents.NOTE_BLOCK_PLING.value(), 0, 1);
+
+    @ConfigValue
+    public static boolean blockExpensiveRerolls = false;
 }
- removed wither essence fix (was fixed by admins so not needed) - fixed key notification - fixed compactNumbers not working with overload - hopefully fixed relics not working when holding a termGabriel Ellnebrand · 2026-09-06 · f2598f4
查看变更
src/main/java/blade/addon/features/dungeon/KeyNotification.java     |  63 ++++++++++++++++++++++-----------------------------------------
 src/main/java/blade/addon/features/dungeon/f7/relic/RelicSpawn.java |   4 ++--
 src/main/java/blade/addon/features/other/CompactDamageNumbers.java  |   7 ++++---
 src/main/java/blade/addon/mixin/SkullBlockRendererMixin.java        |  44 --------------------------------------------
 src/main/java/blade/addon/utils/config/Config.java                  |   1 -
 src/main/java/blade/addon/utils/config/values/Visual.java           |   3 ---
 src/main/resources/assets/blade-addons/textures/entity/essence.png  | Bin 1018 -> 0 bytes
 src/main/resources/blade-addons.mixins.json                         |   3 +--
 8 files changed, 29 insertions(+), 96 deletions(-)
diff --git a/src/main/java/blade/addon/features/dungeon/KeyNotification.java b/src/main/java/blade/addon/features/dungeon/KeyNotification.java
index bba1d23..9ed5021 100644
--- a/src/main/java/blade/addon/features/dungeon/KeyNotification.java
+++ b/src/main/java/blade/addon/features/dungeon/KeyNotification.java
@@ -9,18 +9,11 @@ import blade.addon.utils.dungeon.DungeonClass;
 import blade.addon.utils.dungeon.Phase;
 import blade.addon.utils.events.Events;
 import config.practical.hud.HUDCategory;
-import net.fabricmc.fabric.api.client.event.lifecycle.v1.ClientTickEvents;
-import net.minecraft.core.component.DataComponents;
 import net.minecraft.network.chat.Component;
 import net.minecraft.sounds.SoundEvents;
-import net.minecraft.world.entity.Entity;
-import net.minecraft.world.entity.EquipmentSlot;
 import net.minecraft.world.entity.decoration.ArmorStand;
-import net.minecraft.world.item.ItemStack;
-import net.minecraft.world.item.component.ResolvableProfile;
 
 import java.util.List;
-import java.util.concurrent.CopyOnWriteArrayList;
 import java.util.regex.Matcher;
 import java.util.regex.Pattern;
 
@@ -29,15 +22,9 @@ public class KeyNotification extends CombineableNotification {
     private final static Component BLOOD_KEY = Component.literal("§l§cBlood Key Dropped");
     private final static Component WITHER_KEY = Component.literal("§l§7Wither Key Dropped");
 
-    //Thanks to mio for this key detection system
-    private final static String WITHER_UUID = "2865274b-3097-394e-8149-ec629c72d850";
-    private final static String BLOOD_UUID = "73f6d1f9-df41-3d1d-b98c-e1442d915885";
-
     private final static Pattern AUTOMATIC_PICKUP_PATTERN = Pattern.compile("A (Wither|Blood) Key was picked up!");
     private final static Pattern NORMAL_PICKUP_PATTERN = Pattern.compile("has obtained (Wither|Blood) Key!");
 
-    private final CopyOnWriteArrayList<Entity> foundKeys = new CopyOnWriteArrayList<>();
-
     private boolean hasKey = false;
     private boolean isBloodKey = false;
 
@@ -46,38 +33,33 @@ public class KeyNotification extends CombineableNotification {
     }
 
     public void init() {
-        ClientTickEvents.END_LEVEL_TICK.register((world) -> {
-            if (!Location.inDungeon() || hasKey || !Dungeons.enableKeyNotifier || Phase.inBoss()) return;
-
-            for (Entity entity : world.entitiesForRendering()) {
-                if (entity instanceof ArmorStand armorStand && !foundKeys.contains(entity)) {
-                    ItemStack head = armorStand.getItemBySlot(EquipmentSlot.HEAD);
-                    ResolvableProfile profile = head.get(DataComponents.PROFILE);
-                    if (profile == null) continue;
-                    String uuid = profile.partialProfile().id().toString();
-                    if (uuid == null) continue;
-
-                    if (uuid.equals(WITHER_UUID)) {
-                        foundKeys.add(entity);
-                        hasKey = true;
-                        if (isValidClass()) {
-                            Scheduler.scheduleSound(SoundEvents.EXPERIENCE_ORB_PICKUP, 2, 0.5f);
-                        }
-                        return;
-                    }
 
-                    if (uuid.equals(BLOOD_UUID)) {
-                        foundKeys.add(entity);
-                        isBloodKey = true;
-                        hasKey = true;
+        Events.ON_ENTITY_TRACKED.register((entity, _) -> {
+            if (!Location.inDungeon() || hasKey || !Dungeons.enableKeyNotifier || Phase.inBoss()) return false;
+
+            if (entity instanceof ArmorStand possibleKey) {
+
+                Component customName = possibleKey.getCustomName();
+                if (customName == null) return false;
 
-                        if (isValidClass()) {
-                            Scheduler.scheduleSound(SoundEvents.EXPERIENCE_ORB_PICKUP, 2, 0.5f);
-                        }
-                        return;
+                String name = customName.getString();
+                if (name.equals("Wither Key")) {
+                    hasKey = true;
+                    if (isValidClass()) {
+                        Scheduler.scheduleSound(SoundEvents.EXPERIENCE_ORB_PICKUP, 2, 0.5f);
+                    }
+                } else if (name.equals("Blood Key")) {
+                    isBloodKey = true;
+                    hasKey = true;
+
+                    if (isValidClass()) {
+                        Scheduler.scheduleSound(SoundEvents.EXPERIENCE_ORB_PICKUP, 2, 0.5f);
                     }
                 }
+
             }
+
+            return false;
         });
 
         Events.ON_GAME_MESSAGE.register(message -> {
@@ -104,7 +86,6 @@ public class KeyNotification extends CombineableNotification {
             if (Location.inDungeon()) {
                 isBloodKey = false;
                 hasKey = false;
-                foundKeys.clear();
             }
             return false;
         });
diff --git a/src/main/java/blade/addon/features/dungeon/f7/relic/RelicSpawn.java b/src/main/java/blade/addon/features/dungeon/f7/relic/RelicSpawn.java
index f939b9a..ac25ddc 100644
--- a/src/main/java/blade/addon/features/dungeon/f7/relic/RelicSpawn.java
+++ b/src/main/java/blade/addon/features/dungeon/f7/relic/RelicSpawn.java
@@ -76,7 +76,7 @@ public class RelicSpawn {
             return false;
         });
 
-        Events.ON_LOCATION_CHANGE.register(newLocation -> {
+        Events.ON_LOCATION_CHANGE.register(_ -> {
             if (Location.inDungeon()) {
                 reset();
             }
@@ -158,7 +158,7 @@ public class RelicSpawn {
         if (!isARelicPos(pos)) return false;
 
         //skyblock menu check is there incase the item doesnt get updated
-        if (!ItemUtil.itemHasName(itemStack, "Relic") && !ItemUtil.itemHasName(itemStack, "Skyblock Menu")) {
+        if (!ItemUtil.itemHasName(itemStack, "Relic") && !ItemUtil.itemHasName(itemStack, "Skyblock Menu") && !ItemUtil.itemHasName(itemStack, "Arrow")) {
             if (Floor7.blockIncorrectRelicPlace) {
                 Debug.sendDebugMessage(Component.literal("Name: " + itemStack.getHoverName() + " Item: " + itemStack));
                 Misc.addChatMessage(Component.literal("Blocked a weird click"));
diff --git a/src/main/java/blade/addon/features/other/CompactDamageNumbers.java b/src/main/java/blade/addon/features/other/CompactDamageNumbers.java
index 8e730aa..e1114e1 100644
--- a/src/main/java/blade/addon/features/other/CompactDamageNumbers.java
+++ b/src/main/java/blade/addon/features/other/CompactDamageNumbers.java
@@ -14,7 +14,7 @@ import java.util.regex.Pattern;
 
 public class CompactDamageNumbers {
 
-    private static final Pattern DAMAGE_PATTERN = Pattern.compile("^✧?((?:\\d+,?)*)✧?❤?$");
+    private static final Pattern DAMAGE_PATTERN = Pattern.compile("^✧?✯?((?:\\d+,?)*)✧?✯?❤?$");
 
     private static final String[] CRIT_COLORS = {"§e", "§f", "§c", "§6"};
 
@@ -33,7 +33,7 @@ public class CompactDamageNumbers {
                 if (!matcher.find()) return false;
 
                 String numFound = matcher.group(1);
-                boolean isCrit = name.contains("✧");
+                boolean isCrit = name.contains("✧") || name.contains("✯");
                 boolean loveHit = name.contains("❤");
 
                 if ((isCrit && Visual.hideCrits) || (!isCrit && Visual.hideNoneCrits)) {
@@ -44,7 +44,8 @@ public class CompactDamageNumbers {
                     String formatted = compact(numFound);
 
                     if (isCrit) {
-                        formatted = "✧ " + formatted + " ✧";
+                        char symbol = name.charAt(0);
+                        formatted = symbol + " " + formatted + " " + symbol;
                         formatted = styleCrit(formatted);
                         if (loveHit) formatted += " §d❤";
                         target.setCustomName(Component.literal(formatted));
diff --git a/src/main/java/blade/addon/mixin/SkullBlockRendererMixin.java b/src/main/java/blade/addon/mixin/SkullBlockRendererMixin.java
deleted file mode 100644
index b017a19..0000000
--- a/src/main/java/blade/addon/mixin/SkullBlockRendererMixin.java
+++ /dev/null
@@ -1,44 +0,0 @@
-package blade.addon.mixin;
-
-import blade.addon.utils.Constants;
-import blade.addon.utils.config.values.Visual;
-import com.mojang.authlib.GameProfile;
-import net.minecraft.client.renderer.blockentity.SkullBlockRenderer;
-import net.minecraft.client.renderer.rendertype.RenderType;
-import net.minecraft.resources.Identifier;
-import net.minecraft.world.item.component.ResolvableProfile;
-import net.minecraft.world.level.block.SkullBlock;
-import net.minecraft.world.level.block.entity.SkullBlockEntity;
-import org.jetbrains.annotations.Nullable;
-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.callback.CallbackInfoReturnable;
-
-@Mixin(SkullBlockRenderer.class)
-public abstract class SkullBlockRendererMixin {
-
-    @Unique
-    private static final Identifier ESSENCE_TEXTURE = Identifier.fromNamespaceAndPath(Constants.NAMESPACE, "/textures/entity/essence.png");
-
-    @Shadow
-    public static RenderType getSkullRenderType(SkullBlock.Type type, @Nullable Identifier texture) {
-        return null;
-    }
-
-    @Inject(method = "resolveSkullRenderType", at = @At("HEAD"), cancellable = true)
-    private void renderEssence(SkullBlock.Type skullType, SkullBlockEntity blockEntity, CallbackInfoReturnable<RenderType> cir) {
-        if (!Visual.fixWitherEssence || skullType != SkullBlock.Types.PLAYER) return;
-
-        ResolvableProfile profileComponent = blockEntity.getOwnerProfile();
-        if (profileComponent == null) return;
-
-        GameProfile profile = profileComponent.partialProfile();
-
-        if (profile.id().toString().equals("e0f3e929-869e-3dca-9504-54c666ee6f23")) {
-            cir.setReturnValue(getSkullRenderType(SkullBlock.Types.PLAYER, ESSENCE_TEXTURE));
-        }
-    }
-}
diff --git a/src/main/java/blade/addon/utils/config/Config.java b/src/main/java/blade/addon/utils/config/Config.java
index 19f81d7..db674fc 100644
--- a/src/main/java/blade/addon/utils/config/Config.java
+++ b/src/main/java/blade/addon/utils/config/Config.java
@@ -416,7 +416,6 @@ public class Config {
         visual.add(new ConfigBool(Component.literal("Compact hoppity messages"), () -> Visual.compactHoppityMsgs, bool -> Visual.compactHoppityMsgs = bool));
         visual.add(new ConfigBool(Component.literal("Hide cooldown"), () -> Visual.hideCooldown, bool -> Visual.hideCooldown = bool));
         visual.add(new ConfigBool(Component.literal("Old player head size"), () -> Visual.oldPlayerHead, bool -> Visual.oldPlayerHead = bool));
-        visual.add(new ConfigBool(Component.literal("Fix wither essence"), () -> Visual.fixWitherEssence, bool -> Visual.fixWitherEssence = bool));
         visual.add(new ConfigBool(Component.literal("1.8.9 like fishing bobber"), () -> Visual.oldFishingRod, bool -> Visual.oldFishingRod = bool));
         visual.add(new ConfigBool(Component.literal("Stop shovel swing when flattening dirt"), () -> Visual.stopShovelFlattening, bool -> Visual.stopShovelFlattening = bool));
         visual.add(new ConfigBool(Component.literal("Stop swinging when throwing a pearl"), () -> Visual.stopPearlSwing, bool -> Visual.stopPearlSwing = bool));
diff --git a/src/main/java/blade/addon/utils/config/values/Visual.java b/src/main/java/blade/addon/utils/config/values/Visual.java
index ab28d21..dc843aa 100644
--- a/src/main/java/blade/addon/utils/config/values/Visual.java
+++ b/src/main/java/blade/addon/utils/config/values/Visual.java
@@ -40,9 +40,6 @@ public class Visual {
     @ConfigValue
     public static boolean oldPlayerHead = false;
 
-    @ConfigValue
-    public static boolean fixWitherEssence = false;
-
     @ConfigValue
     public static boolean oldFishingRod = false;
 
diff --git a/src/main/resources/assets/blade-addons/textures/entity/essence.png b/src/main/resources/assets/blade-addons/textures/entity/essence.png
deleted file mode 100644
index 5606501..0000000
Binary files a/src/main/resources/assets/blade-addons/textures/entity/essence.png and /dev/null differ
diff --git a/src/main/resources/blade-addons.mixins.json b/src/main/resources/blade-addons.mixins.json
index 4fad7f5..8ddb782 100644
--- a/src/main/resources/blade-addons.mixins.json
+++ b/src/main/resources/blade-addons.mixins.json
@@ -39,7 +39,6 @@
     "MouseHandlerMixin",
     "MultiPlayerGameModeMixin",
     "RecipeBookComponentMixin",
-    "RecipeBookScreenMixin",
-    "SkullBlockRendererMixin"
+    "RecipeBookScreenMixin"
   ]
 }
\ No newline at end of file
- added compact damage numbers - added hide non crit and crit - added loadout display, notification and soundGabriel Ellnebrand · 2026-09-06 · c79ad28
查看变更
src/main/java/blade/addon/Bladeaddons.java                                |  3 +++
 src/main/java/blade/addon/features/other/CompactDamageNumbers.java        | 99 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 src/main/java/blade/addon/features/other/SwingAnimation.java              |  1 -
 src/main/java/blade/addon/features/other/loadout/LoadoutData.java         | 46 +++++++++++++++++++++++++++++++++++++
 src/main/java/blade/addon/features/other/loadout/LoadoutDisplay.java      | 51 +++++++++++++++++++++++++++++++++++++++++
 src/main/java/blade/addon/features/other/loadout/LoadoutNotification.java | 35 ++++++++++++++++++++++++++++
 src/main/java/blade/addon/utils/config/Config.java                        | 12 +++++++++-
 src/main/java/blade/addon/utils/config/components/Components.java         | 10 +++++++-
 src/main/java/blade/addon/utils/config/values/ExtraOptions.java           | 10 ++++++++
 src/main/java/blade/addon/utils/config/values/Visual.java                 |  9 ++++++++
 10 files changed, 273 insertions(+), 3 deletions(-)
diff --git a/src/main/java/blade/addon/Bladeaddons.java b/src/main/java/blade/addon/Bladeaddons.java
index ec65a14..cbbfafc 100644
--- a/src/main/java/blade/addon/Bladeaddons.java
+++ b/src/main/java/blade/addon/Bladeaddons.java
@@ -20,6 +20,7 @@ import blade.addon.features.item.*;
 import blade.addon.features.notifications.Notifications;
 import blade.addon.features.other.*;
 import blade.addon.features.other.arrow.ArrowSwapper;
+import blade.addon.features.other.loadout.LoadoutData;
 import blade.addon.features.other.pet.SelectedPet;
 import blade.addon.features.sound.BonzoSound;
 import blade.addon.features.sound.TubaSound;
@@ -109,5 +110,7 @@ public class Bladeaddons implements ModInitializer {
         DeathMessage.init();
         BlockPetMenu.init();
         ToolTip.init();
+        CompactDamageNumbers.init();
+        LoadoutData.init();
     }
 }
\ No newline at end of file
diff --git a/src/main/java/blade/addon/features/other/CompactDamageNumbers.java b/src/main/java/blade/addon/features/other/CompactDamageNumbers.java
new file mode 100644
index 0000000..8e730aa
--- /dev/null
+++ b/src/main/java/blade/addon/features/other/CompactDamageNumbers.java
@@ -0,0 +1,99 @@
+package blade.addon.features.other;
+
+import blade.addon.utils.config.values.Visual;
+import blade.addon.utils.events.Events;
+import blade.addon.utils.rendering.RenderUtils;
+import net.minecraft.network.chat.Component;
+import net.minecraft.network.chat.MutableComponent;
+import net.minecraft.network.chat.Style;
+import net.minecraft.world.entity.decoration.ArmorStand;
+
+import java.util.List;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+public class CompactDamageNumbers {
+
+    private static final Pattern DAMAGE_PATTERN = Pattern.compile("^✧?((?:\\d+,?)*)✧?❤?$");
+
+    private static final String[] CRIT_COLORS = {"§e", "§f", "§c", "§6"};
+
+    public static void init() {
+
+        Events.ON_ENTITY_TRACKED.register((entity, _) -> {
+
+            if (entity instanceof ArmorStand target) {
+                if (!target.hasCustomName() || !target.isCustomNameVisible()) return false;
+
+                Component component = target.getCustomName();
+                assert component != null;
+                String name = component.getString();
+
+                Matcher matcher = DAMAGE_PATTERN.matcher(name);
+                if (!matcher.find()) return false;
+
+                String numFound = matcher.group(1);
+                boolean isCrit = name.contains("✧");
+                boolean loveHit = name.contains("❤");
+
+                if ((isCrit && Visual.hideCrits) || (!isCrit && Visual.hideNoneCrits)) {
+                    target.setCustomNameVisible(false);
+                    return false;
+
+                } else if (Visual.compactDamage) {
+                    String formatted = compact(numFound);
+
+                    if (isCrit) {
+                        formatted = "✧ " + formatted + " ✧";
+                        formatted = styleCrit(formatted);
+                        if (loveHit) formatted += " §d❤";
+                        target.setCustomName(Component.literal(formatted));
+
+                    } else {
+                        MutableComponent newNum = Component.literal(formatted);
+                        Style style = getStyle(component);
+                        if (style != null) {
+                            newNum.setStyle(style);
+                        }
+                        target.setCustomName(newNum);
+
+                    }
+                }
+            }
+
+            return false;
+        });
+    }
+
+    private static String compact(String numFound) {
+        String numOnly = numFound.replace(",", "");
+
+        try {
+            float num = Float.parseFloat(numOnly);
+            return RenderUtils.formatNumber(num);
+        } catch (NumberFormatException e) {
+            return numFound;
+        }
+    }
+
+    private static Style getStyle(Component component) {
+        List<Component> siblings = component.getSiblings();
+        if (siblings.isEmpty()) return null;
+        return siblings.getFirst().getStyle();
+    }
+
+    private static String styleCrit(String crit) {
+        StringBuilder styledCrit = new StringBuilder();
+        int cnt = 0;
+        for (char c : crit.toCharArray()) {
+            if (c != ' ') {
+                styledCrit.append(CRIT_COLORS[cnt % CRIT_COLORS.length]);
+                cnt ++;
+            }
+            styledCrit.append(c);
+        }
+
+        return styledCrit.toString();
+    }
+
+}
diff --git a/src/main/java/blade/addon/features/other/SwingAnimation.java b/src/main/java/blade/addon/features/other/SwingAnimation.java
index 5ee1fee..05cde86 100644
--- a/src/main/java/blade/addon/features/other/SwingAnimation.java
+++ b/src/main/java/blade/addon/features/other/SwingAnimation.java
@@ -20,7 +20,6 @@ public class SwingAnimation {
     public static void consumeSwing() {
         if (ignoreCount > 0) {
             ignoreCount--;
-            System.out.println("Consumed a swing, now at count: " + ignoreCount);
         }
     }
 
diff --git a/src/main/java/blade/addon/features/other/loadout/LoadoutData.java b/src/main/java/blade/addon/features/other/loadout/LoadoutData.java
new file mode 100644
index 0000000..4b1e853
--- /dev/null
+++ b/src/main/java/blade/addon/features/other/loadout/LoadoutData.java
@@ -0,0 +1,46 @@
+package blade.addon.features.other.loadout;
+
+import blade.addon.utils.Misc;
+import blade.addon.utils.config.values.ExtraOptions;
+import blade.addon.utils.events.Events;
+
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+public class LoadoutData {
+
+    private static final Pattern LOADOUT_PATTERN = Pattern.compile("^You equipped (.+)!$");
+
+    private static final long DURATION = 1000;
+
+    private static String current = "Unknown";
+    private static long prevTime = 0;
+
+    public static void init() {
+
+        Events.ON_GAME_MESSAGE.register(text -> {
+
+            if (!ExtraOptions.enableLoadoutDisplay) return false;
+
+            String string = text.getString();
+            Matcher matcher = LOADOUT_PATTERN.matcher(string);
+            if (!matcher.find()) return false;
+
+            current = matcher.group(1);
+            prevTime = System.currentTimeMillis();
+            Misc.sendSound(ExtraOptions.loadoutSound);
+
+            return false;
+        });
+
+    }
+
+    public static String getCurrentLoadout() {
+        return current;
+    }
+
+    public static boolean shouldDisplayNotification() {
+        return System.currentTimeMillis() - prevTime < DURATION;
+    }
+
+}
diff --git a/src/main/java/blade/addon/features/other/loadout/LoadoutDisplay.java b/src/main/java/blade/addon/features/other/loadout/LoadoutDisplay.java
new file mode 100644
index 0000000..b72ce1e
--- /dev/null
+++ b/src/main/java/blade/addon/features/other/loadout/LoadoutDisplay.java
@@ -0,0 +1,51 @@
+package blade.addon.features.other.loadout;
+
+import blade.addon.utils.config.values.ExtraOptions;
+import blade.addon.utils.rendering.RenderUtils;
+import config.practical.hud.HUDCategory;
+import config.practical.hud.HUDComponent;
+import net.minecraft.client.Minecraft;
+import net.minecraft.client.gui.Font;
+import net.minecraft.client.gui.GuiGraphicsExtractor;
+import org.jspecify.annotations.NonNull;
+
+import java.util.List;
+
+public class LoadoutDisplay extends HUDComponent {
+
+    public LoadoutDisplay() {
+        super("Loadout display");
+    }
+
+    @Override
+    public int getWidth() {
+        return 110;
+    }
+
+    @Override
+    public int getHeight() {
+        return 10;
+    }
+
+    @Override
+    public boolean editable() {
+        return ExtraOptions.enableLoadoutDisplay;
+    }
+
+    @Override
+    public boolean shouldRender() {
+        return ExtraOptions.enableLoadoutDisplay;
+    }
+
+    @Override
+    public List<HUDCategory> categories() {
+        return null;
+    }
+
+    @Override
+    public void render(@NonNull GuiGraphicsExtractor guiGraphicsExtractor) {
+        Font textRenderer = Minecraft.getInstance().font;
+        if (textRenderer == null) return;
+        RenderUtils.drawPrefixedText(this, guiGraphicsExtractor, "Loadout", LoadoutData.getCurrentLoadout());
+    }
+}
diff --git a/src/main/java/blade/addon/features/other/loadout/LoadoutNotification.java b/src/main/java/blade/addon/features/other/loadout/LoadoutNotification.java
new file mode 100644
index 0000000..2a1e54a
--- /dev/null
+++ b/src/main/java/blade/addon/features/other/loadout/LoadoutNotification.java
@@ -0,0 +1,35 @@
+package blade.addon.features.other.loadout;
+
+import blade.addon.utils.config.components.CombineableNotification;
+import blade.addon.utils.config.values.ExtraOptions;
+import config.practical.hud.HUDCategory;
+import net.minecraft.network.chat.Component;
+
+import java.util.List;
+
+public class LoadoutNotification extends CombineableNotification {
+
+    public LoadoutNotification() {
+        super("Loadout notification");
+    }
+
+    @Override
+    public boolean enabled() {
+        return ExtraOptions.enableLoadoutNotification;
+    }
+
+    @Override
+    public Component getText() {
+        return Component.literal("§a" + LoadoutData.getCurrentLoadout());
+    }
+
+    @Override
+    public boolean shouldRender() {
+        return LoadoutData.shouldDisplayNotification();
+    }
+
+    @Override
+    public List<HUDCategory> categories() {
+        return null;
+    }
+}
diff --git a/src/main/java/blade/addon/utils/config/Config.java b/src/main/java/blade/addon/utils/config/Config.java
index 5d16f55..19f81d7 100644
--- a/src/main/java/blade/addon/utils/config/Config.java
+++ b/src/main/java/blade/addon/utils/config/Config.java
@@ -390,8 +390,14 @@ public class Config {
         ss.add(new ConfigDouble(Component.literal("Lucky button rng (0.1)"), () -> ExtraOptions.luckyButtonRng, num -> ExtraOptions.luckyButtonRng = num, 0.01, 0, 1));
         ss.add(new ConfigColor(Component.literal("Lucky button color"), () -> ExtraOptions.luckyButtonColor, color -> ExtraOptions.luckyButtonColor = color, "lucky-button-color", true));
         ss.add(new ConfigSound(Component.literal("Button sound"), ExtraOptions.ssSound));
-
         extra.add(ss);
+
+        ConfigSection loadout = new ConfigSection(Component.literal("Loadout"));
+        loadout.add(new ConfigBool(Component.literal("Enable display"), () -> ExtraOptions.enableLoadoutDisplay, bool -> ExtraOptions.enableLoadoutDisplay = bool));
+        loadout.add(new ConfigBool(Component.literal("Enable notification"), () -> ExtraOptions.enableLoadoutNotification, bool -> ExtraOptions.enableLoadoutNotification = bool));
+        loadout.add(new ConfigSound(Component.literal("Loadout swap sound"), ExtraOptions.loadoutSound));
+        extra.add(loadout);
+
         return extra;
     }
 
@@ -421,6 +427,10 @@ public class Config {
         toolTip.add(new ConfigFloat(Component.literal("Tooltip size"), () -> Visual.tooltipSize, num -> Visual.tooltipSize = num, 0.1f, 0.5f, 2));
         visual.add(toolTip);
 
+        visual.add(new ConfigBool(Component.literal("Hide non crits"), () -> Visual.hideNoneCrits, bool -> Visual.hideNoneCrits = bool));
+        visual.add(new ConfigBool(Component.literal("Hide crits"), () -> Visual.hideCrits, bool -> Visual.hideCrits = bool));
+        visual.add(new ConfigBool(Component.literal("Compact damage numbers"), () -> Visual.compactDamage, bool -> Visual.compactDamage = bool));
+
         return visual;
     }
 
diff --git a/src/main/java/blade/addon/utils/config/components/Components.java b/src/main/java/blade/addon/utils/config/components/Components.java
index bec670a..5aa6e82 100644
--- a/src/main/java/blade/addon/utils/config/components/Components.java
+++ b/src/main/java/blade/addon/utils/config/components/Components.java
@@ -22,6 +22,8 @@ import blade.addon.features.notifications.NotificationDisplay;
 import blade.addon.features.other.*;
 import blade.addon.features.other.arrow.ArrowDisplay;
 import blade.addon.features.other.arrow.ArrowNotification;
+import blade.addon.features.other.loadout.LoadoutDisplay;
+import blade.addon.features.other.loadout.LoadoutNotification;
 import blade.addon.features.other.pet.PetDisplay;
 import blade.addon.features.other.pet.PetNotification;
 import config.practical.hud.HUDComponent;
@@ -99,12 +101,15 @@ public class Components {
     @ConfigValue
     public static CombineableNotification bloodNotificationDisplay = new BloodNotification();
 
+    @ConfigValue
+    public static CombineableNotification loadoutNotification = new LoadoutNotification();
+
     @ConfigValue
     public static HUDComponent combinedNotifications = new CombinedNotification(
             duplicateClassDisplay, keyNotifierDisplay, pre4Notification,
             melodyNotification, stormCrushNotification, crystalReminderNotification,
             chatNotification, petTitleNotification, arrowSwapDisplay,
-            sectionCompletionDisplay, bloodNotificationDisplay);
+            sectionCompletionDisplay, bloodNotificationDisplay, loadoutNotification);
 
     @ConfigValue
     public static HUDComponent relicProgressBar = new RelicProgressBar();
@@ -159,4 +164,7 @@ public class Components {
 
     @ConfigValue
     public static HUDComponent stormLbTimer = new StormLBTimer();
+
+    @ConfigValue
+    public static HUDComponent LoadoutDisplay = new LoadoutDisplay();
 }
diff --git a/src/main/java/blade/addon/utils/config/values/ExtraOptions.java b/src/main/java/blade/addon/utils/config/values/ExtraOptions.java
index 8b2449e..a45bf63 100644
--- a/src/main/java/blade/addon/utils/config/values/ExtraOptions.java
+++ b/src/main/java/blade/addon/utils/config/values/ExtraOptions.java
@@ -144,4 +144,14 @@ public class ExtraOptions {
 
     @ConfigValue
     public static boolean formatMathResult = true;
+
+    @ConfigValue
+    public static boolean enableLoadoutDisplay = false;
+
+    @ConfigValue
+    public static boolean enableLoadoutNotification = false;
+
+    @ConfigValue
+    public static SoundData loadoutSound = new SoundData(SoundEvents.NOTE_BLOCK_PLING.value(), 0, 1);
+
 }
diff --git a/src/main/java/blade/addon/utils/config/values/Visual.java b/src/main/java/blade/addon/utils/config/values/Visual.java
index 6b6ad6e..ab28d21 100644
--- a/src/main/java/blade/addon/utils/config/values/Visual.java
+++ b/src/main/java/blade/addon/utils/config/values/Visual.java
@@ -64,4 +64,13 @@ public class Visual {
     @ConfigValue
     public static float tooltipSize = 1;
 
+    @ConfigValue
+    public static boolean hideNoneCrits = false;
+
+    @ConfigValue
+    public static boolean hideCrits = false;
+
+    @ConfigValue
+    public static boolean compactDamage = false;
+
 }
- added scaleable tooltipsGabriel Ellnebrand · 2026-09-05 · 18259d3
查看变更
src/main/java/blade/addon/mixin/GuiGraphicsExtractorMixin.java | 44 ++++++++++++++++++++++++++++++++++++++++++--
 src/main/java/blade/addon/utils/config/Config.java             |  2 ++
 src/main/java/blade/addon/utils/config/values/Visual.java      |  3 +++
 3 files changed, 47 insertions(+), 2 deletions(-)
diff --git a/src/main/java/blade/addon/mixin/GuiGraphicsExtractorMixin.java b/src/main/java/blade/addon/mixin/GuiGraphicsExtractorMixin.java
index aed8572..dd8db59 100644
--- a/src/main/java/blade/addon/mixin/GuiGraphicsExtractorMixin.java
+++ b/src/main/java/blade/addon/mixin/GuiGraphicsExtractorMixin.java
@@ -1,18 +1,27 @@
 package blade.addon.mixin;
 
 import blade.addon.utils.config.values.Visual;
+import net.minecraft.client.gui.Font;
 import net.minecraft.client.gui.GuiGraphicsExtractor;
+import net.minecraft.client.gui.screens.inventory.tooltip.ClientTooltipComponent;
+import net.minecraft.client.gui.screens.inventory.tooltip.ClientTooltipPositioner;
+import net.minecraft.resources.Identifier;
 import net.minecraft.world.entity.LivingEntity;
 import net.minecraft.world.item.ItemStack;
 import net.minecraft.world.item.Items;
 import org.joml.Matrix3x2fStack;
+import org.jspecify.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.ModifyArgs;
 import org.spongepowered.asm.mixin.injection.ModifyVariable;
 import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
+import org.spongepowered.asm.mixin.injection.invoke.arg.Args;
+
+import java.util.List;
 
 @Mixin(GuiGraphicsExtractor.class)
 public class GuiGraphicsExtractorMixin {
@@ -20,8 +29,7 @@ public class GuiGraphicsExtractorMixin {
     @Final
     @Shadow
     private Matrix3x2fStack pose;
-
-
+    
     @ModifyVariable(method = "itemCooldown", at=@At("STORE"), ordinal = 0)
     private float noCooldown(float f) {
         return Visual.hideCooldown? 0: f;
@@ -46,4 +54,36 @@ public class GuiGraphicsExtractorMixin {
         }
     }
 
+    @Inject(method = "tooltip", at= @At(value = "INVOKE", target = "Lorg/joml/Matrix3x2fStack;pushMatrix()Lorg/joml/Matrix3x2fStack;"))
+    private void scaleUpTooltip_head(Font font, List<ClientTooltipComponent> lines, int xo, int yo, ClientTooltipPositioner positioner, @Nullable Identifier style, CallbackInfo ci) {
+        pose.scale(Visual.tooltipSize);
+    }
+
+    @ModifyArgs(method = "tooltip", at= @At(value = "INVOKE", target = "Lnet/minecraft/client/gui/screens/inventory/tooltip/ClientTooltipPositioner;positionTooltip(IIIIII)Lorg/joml/Vector2ic;"))
+    private void scaleUpTooltip(Args args) {
+
+        //Yeah, this is a bad way to do this, but I'm lazy
+
+        int sw = args.get(0);
+        int sh = args.get(1);
+        int x = args.get(2);
+        int y = args.get(3);
+        int w = args.get(4);
+        int h = args.get(5);
+
+        float scaledSW = (float)sw / Visual.tooltipSize;
+        float scaledSH = (float)sh / Visual.tooltipSize;
+        float scaledX = (float)x / Visual.tooltipSize;
+        float scaledY = (float)y / Visual.tooltipSize;
+        float scaledW = (float)w / Visual.tooltipSize;
+        float scaledH = (float)h / Visual.tooltipSize;
+
+        args.set(0, (int)scaledSW);
+        args.set(1, (int)scaledSH);
+        args.set(2, (int)scaledX);
+        args.set(3, (int)scaledY);
+        args.set(4, (int)scaledW);
+        args.set(5, (int)scaledH);
+    }
+
 }
diff --git a/src/main/java/blade/addon/utils/config/Config.java b/src/main/java/blade/addon/utils/config/Config.java
index 6e991e3..5d16f55 100644
--- a/src/main/java/blade/addon/utils/config/Config.java
+++ b/src/main/java/blade/addon/utils/config/Config.java
@@ -20,6 +20,7 @@ import config.practical.widgets.*;
 import config.practical.widgets.color.ConfigColor;
 import config.practical.widgets.options.ConfigOptions;
 import config.practical.widgets.sliders.ConfigDouble;
+import config.practical.widgets.sliders.ConfigFloat;
 import config.practical.widgets.sliders.ConfigInt;
 import config.practical.widgets.sound.ConfigSound;
 import net.minecraft.client.Minecraft;
@@ -417,6 +418,7 @@ public class Config {
         ConfigSection toolTip = new ConfigSection(Component.literal("ToolTips"));
         toolTip.add(new ConfigBool(Component.literal("Item quality"), () -> Visual.itemQuality, bool -> Visual.itemQuality = bool));
         toolTip.add(new ConfigBool(Component.literal("Pet candy"), () -> Visual.petCandy, bool -> Visual.petCandy = bool));
+        toolTip.add(new ConfigFloat(Component.literal("Tooltip size"), () -> Visual.tooltipSize, num -> Visual.tooltipSize = num, 0.1f, 0.5f, 2));
         visual.add(toolTip);
 
         return visual;
diff --git a/src/main/java/blade/addon/utils/config/values/Visual.java b/src/main/java/blade/addon/utils/config/values/Visual.java
index 1d7b0f2..6b6ad6e 100644
--- a/src/main/java/blade/addon/utils/config/values/Visual.java
+++ b/src/main/java/blade/addon/utils/config/values/Visual.java
@@ -61,4 +61,7 @@ public class Visual {
     @ConfigValue
     public static boolean petCandy = false;
 
+    @ConfigValue
+    public static float tooltipSize = 1;
+
 }
- fixed goldor color - fixed hide playerGabriel Ellnebrand · 2026-09-01 · 9761bcf
查看变更
gradle.properties                                                        |  2 +-
 src/main/java/blade/addon/features/dungeon/HidePlayers.java              |  2 +-
 src/main/java/blade/addon/features/dungeon/f7/terms/GoldorTickTimer.java |  2 +-
 src/main/java/blade/addon/features/other/PracticeSS.java                 | 10 ++++++----
 4 files changed, 9 insertions(+), 7 deletions(-)
diff --git a/gradle.properties b/gradle.properties
index 285f5be..3e9977b 100644
--- a/gradle.properties
+++ b/gradle.properties
@@ -9,7 +9,7 @@ minecraft_version=26.1.2
 loader_version=0.19.3
 loom_version=1.17-SNAPSHOT
 # Mod Properties
-mod_version=2.5.2
+mod_version=2.5.3
 maven_group=blade.addon
 archives_base_name=blade-addons
 # Dependencies
diff --git a/src/main/java/blade/addon/features/dungeon/HidePlayers.java b/src/main/java/blade/addon/features/dungeon/HidePlayers.java
index 6f14e5d..6a167a7 100644
--- a/src/main/java/blade/addon/features/dungeon/HidePlayers.java
+++ b/src/main/java/blade/addon/features/dungeon/HidePlayers.java
@@ -57,7 +57,7 @@ public class HidePlayers {
     }
 
     public static boolean shouldHidePlayers(Player player) {
-        if (!Location.inDungeon() || !EntityUtil.isARealPlayer(player) || EntityUtil.isClientPlayer(player) || Phase.runStarted()) return false;
+        if (!Location.inDungeon() || !EntityUtil.isARealPlayer(player) || EntityUtil.isClientPlayer(player)) return false;
 
         if (testHideAtLeap()) {
             return true;
diff --git a/src/main/java/blade/addon/features/dungeon/f7/terms/GoldorTickTimer.java b/src/main/java/blade/addon/features/dungeon/f7/terms/GoldorTickTimer.java
index a8cf48e..ea786a8 100644
--- a/src/main/java/blade/addon/features/dungeon/f7/terms/GoldorTickTimer.java
+++ b/src/main/java/blade/addon/features/dungeon/f7/terms/GoldorTickTimer.java
@@ -50,7 +50,7 @@ public class GoldorTickTimer extends CombineableTickTimer {
 
     @Override
     public int getColor() {
-        double mod = getTime();
+        double mod = getTime() % 3;
         return  (mod < 1 ? Constants.GREEN : mod < 2 ? Constants.GOLD : Constants.RED);
     }
 
diff --git a/src/main/java/blade/addon/features/other/PracticeSS.java b/src/main/java/blade/addon/features/other/PracticeSS.java
index a6729ae..decfc62 100644
--- a/src/main/java/blade/addon/features/other/PracticeSS.java
+++ b/src/main/java/blade/addon/features/other/PracticeSS.java
@@ -47,11 +47,13 @@ public class PracticeSS {
     private static final int UNLUCKY_DURATION = 36;
     private static final int START_WAIT_DURATION = 6;
 
+    private static final int PHASES = 4;
+
     private static boolean started = false;
     private static boolean showingPattern = false;
 
     private static int currentIndex = 0;
-    private static int endIndex = 4;
+    private static int endIndex = 3;
 
     private static int ticksLeft = 0;
     private static int totalTicks = 0;
@@ -68,7 +70,7 @@ public class PracticeSS {
 
     public static void init() {
 
-        Events.ON_BLOCK_INTERACTION.register((BlockHitResult result, ItemStack item) -> {
+        Events.ON_BLOCK_INTERACTION.register((BlockHitResult result, ItemStack _) -> {
             if (!ExtraOptions.practiceSSAnywhere && !Location.in(Location.PRIVATE_ISLAND)) return false;
             ClientLevel world = Minecraft.getInstance().level;
             if (world == null) return false;
@@ -113,7 +115,7 @@ public class PracticeSS {
         });
 
         RenderingEvents.FILLED.register(PracticeSS::render);
-        Events.ON_LOCATION_CHANGE.register(location -> {
+        Events.ON_LOCATION_CHANGE.register(_ -> {
             reset();
             return false;
         });
@@ -154,7 +156,7 @@ public class PracticeSS {
             Misc.sendSound(ExtraOptions.ssSound);
 
 
-            if (currentIndex == 5) {
+            if (currentIndex == PHASES) {
                 recordTime();
             } else if (currentIndex == endIndex) {
                 incStage();
- overhauled hudcomponents (added categories, needs testing) - updated gradleGabriel Ellnebrand · 2026-09-01 · 80480c6
查看变更
build.gradle                                                                                        |  16 ++-----
 gradle.properties                                                                                   |   4 +-
 gradle/wrapper/gradle-wrapper.jar                                                                   | Bin 45457 -> 47505 bytes
 gradle/wrapper/gradle-wrapper.properties                                                            |   7 ++-
 gradlew                                                                                             |   6 +--
 gradlew.bat                                                                                         |  35 +++++---------
 src/main/java/blade/addon/Bladeaddons.java                                                          |  51 +++++---------------
 src/main/java/blade/addon/features/dungeon/{BloodNotifier.java => BloodNotification.java}           |  49 ++++++++++++-------
 src/main/java/blade/addon/features/dungeon/ChestCounter.java                                        |  47 ++++++++++++++----
 src/main/java/blade/addon/features/dungeon/HidePlayers.java                                         |   6 +--
 src/main/java/blade/addon/features/dungeon/{KeyNotifier.java => KeyNotification.java}               |  50 +++++++++++--------
 src/main/java/blade/addon/features/dungeon/QuizTimer.java                                           |  49 +++++++++++++++----
 src/main/java/blade/addon/features/dungeon/RunStartValidator.java                                   |  68 +++++++++++++++-----------
 src/main/java/blade/addon/features/dungeon/SecretSpawnTimer.java                                    |  44 +++++++++++++----
 src/main/java/blade/addon/features/dungeon/WarpCooldown.java                                        |  67 ++++++++++++++++++++++----
 src/main/java/blade/addon/features/dungeon/f7/StormLBTimer.java                                     |  55 ++++++++++++++++-----
 src/main/java/blade/addon/features/dungeon/f7/dragons/{DragSpawnTimer.java => DragonSpawn.java}     |  19 +++-----
 src/main/java/blade/addon/features/dungeon/f7/dragons/DragonSpawnTimer.java                         |  48 +++++++++++++++++++
 src/main/java/blade/addon/features/dungeon/f7/dragons/DragonTracer.java                             |   4 +-
 src/main/java/blade/addon/features/dungeon/f7/invincibility/InvincibilityDisplay.java               | 136 ++++++++++++++++++++++++++++++++++++++++++++++++++++
 src/main/java/blade/addon/features/dungeon/f7/invincibility/InvincibilityDuration.java              |  49 +++++++++++++++----
 src/main/java/blade/addon/features/dungeon/f7/invincibility/InvincibilityTimer.java                 | 102 +++++----------------------------------
 src/main/java/blade/addon/features/dungeon/f7/location/LocationNotifier.java                        |  60 +++++++++++++++++------
 src/main/java/blade/addon/features/dungeon/f7/maxor/MaxorStun.java                                  |  47 ++++++++++++++----
 src/main/java/blade/addon/features/dungeon/f7/maxor/MaxorTickTimer.java                             |  44 ++++++++++++++---
 src/main/java/blade/addon/features/dungeon/f7/maxor/crystals/CrystalNotification.java               |  36 ++++++++++++++
 src/main/java/blade/addon/features/dungeon/f7/maxor/{ => crystals}/CrystalSpawn.java                |  23 ++-------
 src/main/java/blade/addon/features/dungeon/f7/maxor/crystals/CrystalTimer.java                      |  43 +++++++++++++++++
 src/main/java/blade/addon/features/dungeon/f7/relic/RelicProgressBar.java                           |  79 ++++++++++++++++++++++++++++++
 src/main/java/blade/addon/features/dungeon/f7/{RelicTimer.java => relic/RelicSpawn.java}            |  91 +++++++----------------------------
 src/main/java/blade/addon/features/dungeon/f7/relic/RelicTimer.java                                 |  47 ++++++++++++++++++
 src/main/java/blade/addon/features/dungeon/f7/storm/DistanceToLedge.java                            |  57 ++++++++++++++++------
 src/main/java/blade/addon/features/dungeon/f7/storm/StormDeathTime.java                             |  88 ++++++++++++++++++++++++++++++++++
 src/main/java/blade/addon/features/dungeon/f7/storm/StormTickTimer.java                             |  94 +++++++++++-------------------------
 src/main/java/blade/addon/features/dungeon/f7/storm/StormTime.java                                  |  38 +++++++++++++++
 src/main/java/blade/addon/features/dungeon/f7/storm/{ => pillar}/PillarExplode.java                 |  26 +---------
 src/main/java/blade/addon/features/dungeon/f7/storm/pillar/PillarExplodeTimer.java                  |  50 +++++++++++++++++++
 src/main/java/blade/addon/features/dungeon/f7/storm/pillar/PillarNotification.java                  |  37 ++++++++++++++
 src/main/java/blade/addon/features/dungeon/f7/terms/CurrentSection.java                             |  45 ++++++++++++++---
 src/main/java/blade/addon/features/dungeon/f7/terms/GoldorTickTimer.java                            |  47 +++++++++++++-----
 src/main/java/blade/addon/features/dungeon/f7/terms/{LeapNotification.java => LeapDisplay.java}     |  73 +++++++++++++++++++---------
 src/main/java/blade/addon/features/dungeon/f7/terms/{MelodyWarning.java => MelodyNotification.java} |  56 ++++++++++++++--------
 src/main/java/blade/addon/features/dungeon/f7/terms/SectionCompleteNotification.java                |  49 +++++++++++++++++++
 src/main/java/blade/addon/features/dungeon/f7/terms/SectionCompletion.java                          |  32 -------------
 src/main/java/blade/addon/features/dungeon/f7/terms/SectionProgress.java                            |  88 +++++++++++++++++++++++-----------
 src/main/java/blade/addon/features/dungeon/f7/terms/TermStartTimer.java                             |  45 ++++++++++++-----
 src/main/java/blade/addon/features/dungeon/f7/terms/device/DeviceNotification.java                  |  36 ++++++++++++++
 src/main/java/blade/addon/features/dungeon/f7/terms/{ => device}/DeviceNotifier.java                |  18 ++-----
 src/main/java/blade/addon/features/item/HeldItemToolTip.java                                        |  51 +++++++++++++++-----
 src/main/java/blade/addon/features/notifications/NotificationDisplay.java                           |  34 +++++++++++++
 src/main/java/blade/addon/features/notifications/Notifications.java                                 |  20 +++-----
 src/main/java/blade/addon/features/other/KickedTimer.java                                           |  40 ++++++++++++++--
 src/main/java/blade/addon/features/other/RagDisplay.java                                            |  42 +++++++++++++---
 src/main/java/blade/addon/features/other/ReaperDisplay.java                                         |  43 ++++++++++++++---
 src/main/java/blade/addon/features/other/arrow/ArrowDisplay.java                                    |  47 ++++++++++++++++++
 src/main/java/blade/addon/features/other/arrow/ArrowNotification.java                               |  35 ++++++++++++++
 src/main/java/blade/addon/features/other/{ => arrow}/ArrowSwapper.java                              |  23 ++-------
 src/main/java/blade/addon/features/other/pet/PetDisplay.java                                        |  67 ++++++++++++++++++++++++++
 src/main/java/blade/addon/features/other/pet/PetNotification.java                                   |  34 +++++++++++++
 src/main/java/blade/addon/features/other/{ => pet}/SelectedPet.java                                 |  51 ++++----------------
 src/main/java/blade/addon/mixin/GuiMixin.java                                                       |  10 ++--
 src/main/java/blade/addon/mixin/MultiPlayerGameModeMixin.java                                       |   2 +-
 src/main/java/blade/addon/utils/Location.java                                                       |   2 +-
 src/main/java/blade/addon/utils/config/Config.java                                                  |   7 ++-
 src/main/java/blade/addon/utils/config/components/Categories.java                                   |  16 +++++++
 src/main/java/blade/addon/utils/config/components/CombineableNotification.java                      |  47 ++++++++++++++++++
 src/main/java/blade/addon/utils/config/components/CombineableTickTimer.java                         |  48 +++++++++++++++++++
 src/main/java/blade/addon/utils/config/components/CombinedNotification.java                         |  79 ++++++++++++++++++++++++++++++
 src/main/java/blade/addon/utils/config/components/CombinedScreenNotifications.java                  |  79 ------------------------------
 src/main/java/blade/addon/utils/config/components/CombinedTickTimer.java                            |  94 +++++++++++++++++++++++-------------
 src/main/java/blade/addon/utils/config/components/Components.java                                   | 121 +++++++++++++++++++++++-----------------------
 src/main/java/blade/addon/utils/config/values/Floor7.java                                           |   4 +-
 src/main/java/blade/addon/utils/debug/Debug.java                                                    |  35 ++++++--------
 src/main/java/blade/addon/utils/dungeon/DungeonClass.java                                           |   6 +--
 src/main/java/blade/addon/utils/dungeon/Phase.java                                                  |   5 +-
 src/main/java/blade/addon/utils/dungeon/Section.java                                                |   5 +-
 76 files changed, 2311 insertions(+), 997 deletions(-)
diff --git a/build.gradle b/build.gradle
index 102c239..2ef553e 100644
--- a/build.gradle
+++ b/build.gradle
@@ -34,31 +34,23 @@ loom {
 }
 
 repositories {
-
+    mavenCentral()
     maven { url = "https://pkgs.dev.azure.com/djtheredstoner/DevAuth/_packaging/public/maven/v1" }
     maven { url = 'https://jitpack.io' }
-    maven {
-        name = "Terraformers"
-        url = "https://maven.terraformersmc.com/"
-    }
+    maven { url = "https://maven.terraformersmc.com/" }
     maven { url = 'https://repo.hypixel.net/repository/Hypixel/' }
 
 }
 
 dependencies {
-    // To change the versions see the gradle.properties file
     minecraft "com.mojang:minecraft:${project.minecraft_version}"
-    //mappings "net.fabricmc:yarn:${project.yarn_mappings}:v2"
     implementation "net.fabricmc:fabric-loader:${project.loader_version}"
-
-    // Fabric API. This is technically optional, but you probably want it anyway.
     implementation "net.fabricmc.fabric-api:fabric-api:${project.fabric_api_version}"
-    implementation("com.terraformersmc:modmenu:${project.modmenu_version}")
     include implementation("net.hypixel:mod-api:${project.hypixel_api_version}")
     include implementation("com.github.BladeMasterGabe:practical-config:${project.practical_config}")
 
     runtimeOnly("me.djtheredstoner:DevAuth-fabric:${project.dev_auth_version}")
-
+    compileOnly("com.terraformersmc:modmenu:${project.modmenu_version}")
 
 }
 
@@ -89,7 +81,7 @@ jar {
     inputs.property "projectName", projectName
 
     from("LICENSE") {
-        rename { "${it}_$projectName"}
+        rename { "${it}_$projectName" }
     }
 }
 
diff --git a/gradle.properties b/gradle.properties
index 46e3427..285f5be 100644
--- a/gradle.properties
+++ b/gradle.properties
@@ -13,9 +13,9 @@ mod_version=2.5.2
 maven_group=blade.addon
 archives_base_name=blade-addons
 # Dependencies
-fabric_api_version=0.152.1+26.1.2
+fabric_api_version=0.155.2+26.1.2
 # I love dependencies sometimes breaking
-practical_config=902a663c1b6c8917d02f12b7608eb373a9d3c1f3
+practical_config=757ad46ab1b257d494cc150261736b6f9b0e131c
 modmenu_version=18.0.0-beta.1
 hypixel_api_version=1.0.2
 dev_auth_version=1.2.2
diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar
index 8bdaf60..eddabd2 100644
Binary files a/gradle/wrapper/gradle-wrapper.jar and b/gradle/wrapper/gradle-wrapper.jar differ
diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties
index 272ea02..ad7845b 100644
--- a/gradle/wrapper/gradle-wrapper.properties
+++ b/gradle/wrapper/gradle-wrapper.properties
@@ -1,6 +1,9 @@
-#Thu Jun 25 03:30:47 CEST 2026
 distributionBase=GRADLE_USER_HOME
 distributionPath=wrapper/dists
-distributionUrl=https\://services.gradle.org/distributions/gradle-9.6.0-bin.zip
+distributionUrl=https\://services.gradle.org/distributions/gradle-9.7.1-bin.zip
+networkTimeout=10000
+retries=0
+retryBackOffMs=500
+validateDistributionUrl=true
 zipStoreBase=GRADLE_USER_HOME
 zipStorePath=wrapper/dists
diff --git a/gradlew b/gradlew
index adff685..249efbb 100755
--- a/gradlew
+++ b/gradlew
@@ -20,7 +20,7 @@
 
 ##############################################################################
 #
-#   Gradle start up script for POSIX generated by Gradle.
+#   gradlew start up script for POSIX generated by Gradle.
 #
 #   Important for running:
 #
@@ -29,7 +29,7 @@
 #       bash, then to run this script, type that shell name before the whole
 #       command line, like:
 #
-#           ksh Gradle
+#           ksh gradlew
 #
 #       Busybox and similar reduced shells will NOT work, because this script
 #       requires all of these POSIX shell features:
@@ -57,7 +57,7 @@
 #       Darwin, MinGW, and NonStop.
 #
 #   (3) This script is generated from the Groovy template
-#       https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
+#       https://github.com/gradle/gradle/blob/3d91ce3b8caaf77ad09f381f43615b715b53f72c/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
 #       within the Gradle project.
 #
 #       You can find Gradle at https://github.com/gradle/gradle/.
diff --git a/gradlew.bat b/gradlew.bat
index c4bdd3a..a51ec4f 100644
--- a/gradlew.bat
+++ b/gradlew.bat
@@ -19,12 +19,12 @@
 @if "%DEBUG%"=="" @echo off
 @rem ##########################################################################
 @rem
-@rem  Gradle startup script for Windows
+@rem  gradlew startup script for Windows
 @rem
 @rem ##########################################################################
 
-@rem Set local scope for the variables with windows NT shell
-if "%OS%"=="Windows_NT" setlocal
+@rem Set local scope for the variables, and ensure extensions are enabled
+setlocal EnableExtensions
 
 set DIRNAME=%~dp0
 if "%DIRNAME%"=="" set DIRNAME=.
@@ -51,7 +51,7 @@ echo. 1>&2
 echo Please set the JAVA_HOME variable in your environment to match the 1>&2
 echo location of your Java installation. 1>&2
 
-goto fail
+"%COMSPEC%" /c exit 1
 
 :findJavaFromJavaHome
 set JAVA_HOME=%JAVA_HOME:"=%
@@ -65,29 +65,18 @@ echo. 1>&2
 echo Please set the JAVA_HOME variable in your environment to match the 1>&2
 echo location of your Java installation. 1>&2
 
-goto fail
+"%COMSPEC%" /c exit 1
 
 :execute
 @rem Setup the command line
 
 
 
-@rem Execute Gradle
-"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %*
+@rem Execute gradlew
+@rem endlocal doesn't take effect until after the line is parsed and variables are expanded
+@rem which allows us to clear the local environment before executing the java command
+endlocal & "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %* & call :exitWithErrorLevel
 
-:end
-@rem End local scope for the variables with windows NT shell
-if %ERRORLEVEL% equ 0 goto mainEnd
-
-:fail
-rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
-rem the _cmd.exe /c_ return code!
-set EXIT_CODE=%ERRORLEVEL%
-if %EXIT_CODE% equ 0 set EXIT_CODE=1
-if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE%
-exit /b %EXIT_CODE%
-
-:mainEnd
-if "%OS%"=="Windows_NT" endlocal
-
-:omega
+:exitWithErrorLevel
+@rem Use "%COMSPEC%" /c exit to allow operators to work properly in scripts
+"%COMSPEC%" /c exit %ERRORLEVEL%
diff --git a/src/main/java/blade/addon/Bladeaddons.java b/src/main/java/blade/addon/Bladeaddons.java
index d84fcd2..ec65a14 100644
--- a/src/main/java/blade/addon/Bladeaddons.java
+++ b/src/main/java/blade/addon/Bladeaddons.java
@@ -3,28 +3,24 @@ package blade.addon;
 import blade.addon.features.dungeon.*;
 import blade.addon.features.dungeon.f7.BossWaypoints;
 import blade.addon.features.dungeon.f7.PredevTimer;
-import blade.addon.features.dungeon.f7.RelicTimer;
-import blade.addon.features.dungeon.f7.StormLBTimer;
-import blade.addon.features.dungeon.f7.dragons.DragSpawnTimer;
 import blade.addon.features.dungeon.f7.dragons.DragonHealth;
+import blade.addon.features.dungeon.f7.dragons.DragonSpawn;
 import blade.addon.features.dungeon.f7.dragons.DragonTracer;
-import blade.addon.features.dungeon.f7.invincibility.InvincibilityDuration;
 import blade.addon.features.dungeon.f7.invincibility.InvincibilityTimer;
 import blade.addon.features.dungeon.f7.invincibility.MaskHighlight;
-import blade.addon.features.dungeon.f7.location.LocationNotifier;
 import blade.addon.features.dungeon.f7.location.PositionMessages;
-import blade.addon.features.dungeon.f7.maxor.CrystalSpawn;
-import blade.addon.features.dungeon.f7.maxor.MaxorStun;
-import blade.addon.features.dungeon.f7.maxor.MaxorTickTimer;
-import blade.addon.features.dungeon.f7.storm.DistanceToLedge;
-import blade.addon.features.dungeon.f7.storm.PillarExplode;
-import blade.addon.features.dungeon.f7.storm.StormTickTimer;
-import blade.addon.features.dungeon.f7.terms.*;
+import blade.addon.features.dungeon.f7.maxor.crystals.CrystalSpawn;
+import blade.addon.features.dungeon.f7.relic.RelicSpawn;
+import blade.addon.features.dungeon.f7.storm.StormTime;
+import blade.addon.features.dungeon.f7.storm.pillar.PillarExplode;
+import blade.addon.features.dungeon.f7.terms.device.DeviceNotifier;
 import blade.addon.features.filter.Filters;
 import blade.addon.features.highlight.*;
 import blade.addon.features.item.*;
 import blade.addon.features.notifications.Notifications;
 import blade.addon.features.other.*;
+import blade.addon.features.other.arrow.ArrowSwapper;
+import blade.addon.features.other.pet.SelectedPet;
 import blade.addon.features.sound.BonzoSound;
 import blade.addon.features.sound.TubaSound;
 import blade.addon.utils.Commands;
@@ -33,6 +29,7 @@ import blade.addon.utils.Location;
 import blade.addon.utils.Scheduler;
 import blade.addon.utils.config.Config;
 import blade.addon.utils.config.FolderUtility;
+import blade.addon.utils.config.components.Categories;
 import blade.addon.utils.config.components.Components;
 import blade.addon.utils.config.values.Buttons;
 import blade.addon.utils.data.EntityUtil;
@@ -49,6 +46,7 @@ public class Bladeaddons implements ModInitializer {
     @Override
     public void onInitialize() {
         FolderUtility.init();
+        Categories.init();
         Components.init();
         Buttons.init();
         try {
@@ -68,38 +66,25 @@ public class Bladeaddons implements ModInitializer {
 
         RenderingEvents.init();
 
-        StormTickTimer.init();
-        GoldorTickTimer.init();
-        TermStartTimer.init();
+        StormTime.init();
         PositionMessages.init();
         InvincibilityTimer.init();
-        WarpCooldown.init();
         LeapMessage.init();
         ExplosiveShot.init();
-        RunStartValidator.init();
         HidePlayers.init();
         CrystalSpawn.init();
-        KeyNotifier.init();
-        RelicTimer.init();
+        RelicSpawn.init();
         AutoRequeue.init();
         MobHighlight.init();
-        ChestCounter.init();
         Scheduler.init();
-        SecretSpawnTimer.init();
-        DistanceToLedge.init();
         ItemHighlight.init();
         BossWaypoints.init();
         DianaNotifier.init();
-        DragSpawnTimer.init();
+        DragonSpawn.init();
         SelectedPet.init();
-        LocationNotifier.init();
         PredevTimer.init();
-        KickedTimer.init();
         DeviceNotifier.init();
         PillarExplode.init();
-        MelodyWarning.init();
-        RagDisplay.init();
-        LeapNotification.init();
         PracticeSS.init();
         CompactHoppity.init();
         DropAnimation.init();
@@ -108,14 +93,10 @@ public class Bladeaddons implements ModInitializer {
         Notifications.init();
         BatHighlight.init();
         WitherHighlight.init();
-        InvincibilityDuration.init();
         TeammateHighlight.init();
-        SectionProgress.init();
         BonzoSound.init();
-        QuizTimer.init();
         Filters.init();
         DragonHealth.init();
-        MaxorStun.init();
         ArrowSwapper.init();
         StarCountHighlight.init();
         MaskHighlight.init();
@@ -124,13 +105,7 @@ public class Bladeaddons implements ModInitializer {
         SelectedPetHighlight.init();
         SearchBar.init();
         TubaSound.init();
-        SectionCompletion.init();
-        MaxorTickTimer.init();
-        BloodNotifier.init();
-        CurrentSection.init();
         DragonTracer.init();
-        ReaperDisplay.init();
-        StormLBTimer.init();
         DeathMessage.init();
         BlockPetMenu.init();
         ToolTip.init();
diff --git a/src/main/java/blade/addon/features/dungeon/BloodNotifier.java b/src/main/java/blade/addon/features/dungeon/BloodNotification.java
similarity index 56%
rename from src/main/java/blade/addon/features/dungeon/BloodNotifier.java
rename to src/main/java/blade/addon/features/dungeon/BloodNotification.java
index eeb9ed7..d4d5e3a 100644
--- a/src/main/java/blade/addon/features/dungeon/BloodNotifier.java
+++ b/src/main/java/blade/addon/features/dungeon/BloodNotification.java
@@ -1,23 +1,27 @@
 package blade.addon.features.dungeon;
 
-import blade.addon.utils.Constants;
 import blade.addon.utils.Location;
 import blade.addon.utils.Misc;
+import blade.addon.utils.config.components.CombineableNotification;
 import blade.addon.utils.config.values.Dungeons;
 import blade.addon.utils.dungeon.DungeonClass;
 import blade.addon.utils.events.Events;
-import blade.addon.utils.rendering.RenderUtils;
-import config.practical.hud.HUDComponent;
-import net.minecraft.client.gui.GuiGraphicsExtractor;
+import config.practical.hud.HUDCategory;
 import net.minecraft.network.chat.Component;
 import net.minecraft.sounds.SoundEvents;
 
-public class BloodNotifier {
+import java.util.List;
 
-    private static boolean detected = false;
-    private static long prevTime = 0;
+public class BloodNotification extends CombineableNotification {
 
-    public static void init() {
+    private boolean detected = false;
+    private long prevTime = 0;
+
+    public BloodNotification() {
+        super("blood notification");
+    }
+
+    public void init() {
 
         Events.ON_GAME_MESSAGE.register(message -> {
             if (detected || !Location.inDungeon()) return false;
@@ -35,26 +39,37 @@ public class BloodNotifier {
             return false;
         });
 
-        Events.ON_LOCATION_CHANGE.register(newLocation -> {
-            reset();
+        Events.ON_LOCATION_CHANGE.register(_ -> {
+            resetDetection();
             return false;
         });
     }
 
-    private static void reset() {
+    @Override
+    public boolean shouldRender() {
+        return Dungeons.alertBloodSpawns && System.currentTimeMillis() - prevTime < 2000 && correctClass();
+    }
+
+    @Override
+    public List<HUDCategory> categories() {
+        return List.of();
+    }
+
+    private void resetDetection() {
         detected = false;
     }
 
-    private static boolean correctClass() {
+    private boolean correctClass() {
         return Dungeons.alertBloodForAllClasses || DungeonClass.isClass(DungeonClass.MAGE);
     }
 
-    public static boolean display() {
-        return Dungeons.alertBloodSpawns && System.currentTimeMillis() - prevTime < 2000 && correctClass();
+    @Override
+    public boolean enabled() {
+        return Dungeons.alertBloodSpawns;
     }
 
-    public static void render(HUDComponent component, GuiGraphicsExtractor graphics) {
-        RenderUtils.drawCenteredText(graphics, component, Component.literal("First four mobs spawned!"), Constants.RED);
+    @Override
+    public Component getText() {
+        return Component.literal("First four mobs spawned!");
     }
-
 }
diff --git a/src/main/java/blade/addon/features/dungeon/ChestCounter.java b/src/main/java/blade/addon/features/dungeon/ChestCounter.java
index 13d0d31..e4ecf4b 100644
--- a/src/main/java/blade/addon/features/dungeon/ChestCounter.java
+++ b/src/main/java/blade/addon/features/dungeon/ChestCounter.java
@@ -8,16 +8,19 @@ import blade.addon.utils.config.values.Dungeons;
 import blade.addon.utils.dungeon.Phase;
 import blade.addon.utils.events.Events;
 import blade.addon.utils.rendering.RenderUtils;
+import config.practical.hud.HUDCategory;
 import config.practical.hud.HUDComponent;
 import net.minecraft.client.Minecraft;
 import net.minecraft.client.gui.GuiGraphicsExtractor;
 import net.minecraft.network.chat.Component;
 import net.minecraft.sounds.SoundEvents;
+import org.jspecify.annotations.NonNull;
 
+import java.util.List;
 import java.util.regex.Matcher;
 import java.util.regex.Pattern;
 
-public class ChestCounter {
+public class ChestCounter extends HUDComponent {
 
     private static final Pattern PATTERN = Pattern.compile("^ Unclaimed chests: (\\d+)$");
 
@@ -25,8 +28,12 @@ public class ChestCounter {
     private static int countedChests = 0;
     private static boolean hasUpdatedData = false;
 
-    public static void init() {
-        Events.ON_LOCATION_CHANGE.register(newLocation -> {
+    public ChestCounter() {
+        super("Chest count");
+    }
+
+    public void init() {
+        Events.ON_LOCATION_CHANGE.register(_ -> {
             if (Location.in(Location.DUNGEON_HUB)) {
                 hasUpdatedData = false;
             }
@@ -62,19 +69,41 @@ public class ChestCounter {
         });
     }
 
-    public static boolean display() {
+    @Override
+    public int getWidth() {
+        return 110;
+    }
+
+    @Override
+    public int getHeight() {
+        return 10;
+    }
+
+    @Override
+    public boolean editable() {
+        return Dungeons.displayChestCount;
+    }
+
+    @Override
+    public boolean shouldRender() {
         if ((Dungeons.onlyAfterRunOver && !Phase.runOver())) return false;
         return Dungeons.displayChestCount && Location.inDungeon();
     }
 
-    public static void render(HUDComponent component, GuiGraphicsExtractor graphics) {
-        int x = component.getScaledX();
-        int y = component.getScaledY();
+    @Override
+    public List<HUDCategory> categories() {
+        return null;
+    }
+
+    @Override
+    public void render(@NonNull GuiGraphicsExtractor guiGraphicsExtractor) {
+        int x = getScaledX();
+        int y = getScaledY();
 
         if (hasUpdatedData) {
-            RenderUtils.drawPrefixedText(component, graphics, "Chests", Math.min(chestDisplayCount + countedChests, 60) + "");
+            RenderUtils.drawPrefixedText(this, guiGraphicsExtractor, "Chests", Math.min(chestDisplayCount + countedChests, 60) + "");
         } else {
-            graphics.text(Minecraft.getInstance().font, Component.literal("go to the dungeon hub"), x, y, Constants.RED, true);
+            guiGraphicsExtractor.text(Minecraft.getInstance().font, Component.literal("go to the dungeon hub"), x, y, Constants.RED, true);
 
         }
     }
diff --git a/src/main/java/blade/addon/features/dungeon/HidePlayers.java b/src/main/java/blade/addon/features/dungeon/HidePlayers.java
index ed6506f..6f14e5d 100644
--- a/src/main/java/blade/addon/features/dungeon/HidePlayers.java
+++ b/src/main/java/blade/addon/features/dungeon/HidePlayers.java
@@ -1,6 +1,6 @@
 package blade.addon.features.dungeon;
 
-import blade.addon.features.dungeon.f7.terms.DeviceNotifier;
+import blade.addon.features.dungeon.f7.terms.device.DeviceNotifier;
 import blade.addon.utils.Location;
 import blade.addon.utils.config.values.Dungeons;
 import blade.addon.utils.data.EntityUtil;
@@ -19,7 +19,7 @@ public class HidePlayers {
 
 
     public static void init() {
-        Events.ON_LEAP.register(message -> {
+        Events.ON_LEAP.register(_ -> {
             justLeapt = true;
             startTime = System.currentTimeMillis();
             return false;
@@ -57,7 +57,7 @@ public class HidePlayers {
     }
 
     public static boolean shouldHidePlayers(Player player) {
-        if (!Location.inDungeon() || !EntityUtil.isARealPlayer(player) || EntityUtil.isClientPlayer(player)) return false;
+        if (!Location.inDungeon() || !EntityUtil.isARealPlayer(player) || EntityUtil.isClientPlayer(player) || Phase.runStarted()) return false;
 
         if (testHideAtLeap()) {
             return true;
diff --git a/src/main/java/blade/addon/features/dungeon/KeyNotifier.java b/src/main/java/blade/addon/features/dungeon/KeyNotification.java
similarity index 81%
rename from src/main/java/blade/addon/features/dungeon/KeyNotifier.java
rename to src/main/java/blade/addon/features/dungeon/KeyNotification.java
index db47114..bba1d23 100644
--- a/src/main/java/blade/addon/features/dungeon/KeyNotifier.java
+++ b/src/main/java/blade/addon/features/dungeon/KeyNotification.java
@@ -2,15 +2,14 @@ package blade.addon.features.dungeon;
 
 import blade.addon.utils.Location;
 import blade.addon.utils.Scheduler;
+import blade.addon.utils.config.components.Categories;
+import blade.addon.utils.config.components.CombineableNotification;
 import blade.addon.utils.config.values.Dungeons;
 import blade.addon.utils.dungeon.DungeonClass;
 import blade.addon.utils.dungeon.Phase;
 import blade.addon.utils.events.Events;
-import blade.addon.utils.rendering.RenderUtils;
-import config.practical.hud.HUDComponent;
+import config.practical.hud.HUDCategory;
 import net.fabricmc.fabric.api.client.event.lifecycle.v1.ClientTickEvents;
-import net.minecraft.client.Minecraft;
-import net.minecraft.client.gui.GuiGraphicsExtractor;
 import net.minecraft.core.component.DataComponents;
 import net.minecraft.network.chat.Component;
 import net.minecraft.sounds.SoundEvents;
@@ -20,11 +19,12 @@ import net.minecraft.world.entity.decoration.ArmorStand;
 import net.minecraft.world.item.ItemStack;
 import net.minecraft.world.item.component.ResolvableProfile;
 
+import java.util.List;
 import java.util.concurrent.CopyOnWriteArrayList;
 import java.util.regex.Matcher;
 import java.util.regex.Pattern;
 
-public class KeyNotifier {
+public class KeyNotification extends CombineableNotification {
 
     private final static Component BLOOD_KEY = Component.literal("§l§cBlood Key Dropped");
     private final static Component WITHER_KEY = Component.literal("§l§7Wither Key Dropped");
@@ -36,12 +36,16 @@ public class KeyNotifier {
     private final static Pattern AUTOMATIC_PICKUP_PATTERN = Pattern.compile("A (Wither|Blood) Key was picked up!");
     private final static Pattern NORMAL_PICKUP_PATTERN = Pattern.compile("has obtained (Wither|Blood) Key!");
 
-    private final static CopyOnWriteArrayList<Entity> foundKeys = new CopyOnWriteArrayList<>();
+    private final CopyOnWriteArrayList<Entity> foundKeys = new CopyOnWriteArrayList<>();
 
-    private static boolean hasKey = false;
-    private static boolean isBloodKey = false;
+    private boolean hasKey = false;
+    private boolean isBloodKey = false;
 
-    public static void init() {
+    public KeyNotification() {
+        super("Key notifier");
+    }
+
+    public void init() {
         ClientTickEvents.END_LEVEL_TICK.register((world) -> {
             if (!Location.inDungeon() || hasKey || !Dungeons.enableKeyNotifier || Phase.inBoss()) return;
 
@@ -96,7 +100,7 @@ public class KeyNotifier {
             return false;
         });
 
-        Events.ON_LOCATION_CHANGE.register(locations -> {
+        Events.ON_LOCATION_CHANGE.register(_ -> {
             if (Location.inDungeon()) {
                 isBloodKey = false;
                 hasKey = false;
@@ -106,19 +110,27 @@ public class KeyNotifier {
         });
     }
 
-    private static boolean isValidClass() {
-       return (Dungeons.displayKeyForAllClasses || DungeonClass.isClass(DungeonClass.ARCHER) || DungeonClass.isClass(DungeonClass.MAGE) && Dungeons.enableKeyNotifier);
+    @Override
+    public boolean shouldRender() {
+        return Location.inDungeon() && hasKey && isValidClass() && !Phase.inBoss();
     }
 
-    public static boolean display() {
-        return Location.inDungeon() && hasKey && isValidClass() && !Phase.inBoss();
+    @Override
+    public List<HUDCategory> categories() {
+        return List.of(Categories.CLEAR);
     }
 
-    public static void render(HUDComponent component, GuiGraphicsExtractor graphics) {
-        int x = component.getScaledX();
-        int y = component.getScaledY();
+    @Override
+    public boolean enabled() {
+        return Dungeons.enableKeyNotifier;
+    }
+
+    @Override
+    public Component getText() {
+        return isBloodKey ? BLOOD_KEY : WITHER_KEY;
+    }
 
-        Component text = isBloodKey ? BLOOD_KEY : WITHER_KEY;
-        RenderUtils.drawCenteredText(graphics, Minecraft.getInstance().font, text, x, y, component.getWidth(), 0xffffffff);
+    private boolean isValidClass() {
+       return (Dungeons.displayKeyForAllClasses || DungeonClass.isClass(DungeonClass.ARCHER) || DungeonClass.isClass(DungeonClass.MAGE) && Dungeons.enableKeyNotifier);
     }
 }
diff --git a/src/main/java/blade/addon/features/dungeon/QuizTimer.java b/src/main/java/blade/addon/features/dungeon/QuizTimer.java
index 2f75aab..bef9532 100644
--- a/src/main/java/blade/addon/features/dungeon/QuizTimer.java
+++ b/src/main/java/blade/addon/features/dungeon/QuizTimer.java
@@ -2,25 +2,34 @@ package blade.addon.features.dungeon;
 
 import blade.addon.utils.Constants;
 import blade.addon.utils.Location;
+import blade.addon.utils.config.components.Categories;
 import blade.addon.utils.config.values.Dungeons;
 import blade.addon.utils.config.values.ExtraOptions;
 import blade.addon.utils.events.Events;
 import blade.addon.utils.rendering.RenderUtils;
+import config.practical.hud.HUDCategory;
 import config.practical.hud.HUDComponent;
 import net.minecraft.client.Minecraft;
 import net.minecraft.client.gui.Font;
 import net.minecraft.client.gui.GuiGraphicsExtractor;
 import net.minecraft.network.chat.Component;
+import org.jspecify.annotations.NonNull;
 
-public class QuizTimer {
+import java.util.List;
+
+public class QuizTimer extends HUDComponent {
 
     private static final int START_DURATION = 220;
     private static final int QUESTION_DURATION = 100;
 
-    private static int tick = 0;
-    private static int stage = 0;
+    private int tick = 0;
+    private int stage = 0;
+
+    public QuizTimer() {
+        super("Quiz timer");
+    }
 
-    public static void init() {
+    public void init() {
 
         Events.ON_GAME_MESSAGE.register(text -> {
             if (!Location.inDungeon()) return false;
@@ -45,18 +54,40 @@ public class QuizTimer {
             return false;
         });
 
-        Events.ON_LOCATION_CHANGE.register(newLocation -> {
+        Events.ON_LOCATION_CHANGE.register(_ -> {
             tick = 0;
             stage = 0;
             return false;
         });
     }
 
-    public static boolean display() {
+    @Override
+    public int getWidth() {
+        return 80;
+    }
+
+    @Override
+    public int getHeight() {
+        return 10;
+    }
+
+    @Override
+    public boolean editable() {
+        return Dungeons.quizTimer;
+    }
+
+    @Override
+    public boolean shouldRender() {
         return Dungeons.quizTimer && tick > 0;
     }
 
-    public static void render(HUDComponent component, GuiGraphicsExtractor graphics) {
+    @Override
+    public List<HUDCategory> categories() {
+        return List.of(Categories.CLEAR);
+    }
+
+    @Override
+    public void render(@NonNull GuiGraphicsExtractor guiGraphicsExtractor) {
         Font textRenderer = Minecraft.getInstance().font;
         if (textRenderer == null) return;
 
@@ -65,9 +96,9 @@ public class QuizTimer {
                     .append(Component.literal(stage + "/3").withColor(0xffffffff)
                             .append(Component.literal("): ").withColor(ExtraOptions.timerPrefixColor))
                             .append(Component.literal(Constants.DECIMAL_FORMAT.format(tick * Constants.TICK_DURATION) + "s").withColor(0xffffffff)));
-            graphics.text(textRenderer, drawnText, component.getScaledX(), component.getScaledY(), 0xffffffff, true);
+            guiGraphicsExtractor.text(textRenderer, drawnText, getScaledX(), getScaledY(), 0xffffffff, true);
         } else {
-            RenderUtils.drawPrefixedTimer(component, graphics, "Quiz", tick);
+            RenderUtils.drawPrefixedTimer(this, guiGraphicsExtractor, "Quiz", tick);
         }
     }
 
diff --git a/src/main/java/blade/addon/features/dungeon/RunStartValidator.java b/src/main/java/blade/addon/features/dungeon/RunStartValidator.java
index 129271b..fc8a190 100644
--- a/src/main/java/blade/addon/features/dungeon/RunStartValidator.java
+++ b/src/main/java/blade/addon/features/dungeon/RunStartValidator.java
@@ -2,14 +2,14 @@ package blade.addon.features.dungeon;
 
 import blade.addon.utils.Location;
 import blade.addon.utils.Scheduler;
+import blade.addon.utils.config.components.Categories;
+import blade.addon.utils.config.components.CombineableNotification;
 import blade.addon.utils.config.values.Dungeons;
 import blade.addon.utils.data.PartyUtil;
 import blade.addon.utils.dungeon.Phase;
 import blade.addon.utils.events.Events;
-import blade.addon.utils.rendering.RenderUtils;
-import config.practical.hud.HUDComponent;
+import config.practical.hud.HUDCategory;
 import net.minecraft.client.Minecraft;
-import net.minecraft.client.gui.GuiGraphicsExtractor;
 import net.minecraft.client.multiplayer.ClientLevel;
 import net.minecraft.client.player.LocalPlayer;
 import net.minecraft.network.chat.Component;
@@ -18,11 +18,12 @@ import net.minecraft.world.scores.PlayerTeam;
 import net.minecraft.world.scores.Scoreboard;
 
 import java.util.HashMap;
+import java.util.List;
 import java.util.Map;
 import java.util.regex.Matcher;
 import java.util.regex.Pattern;
 
-public class RunStartValidator {
+public class RunStartValidator extends CombineableNotification {
 
     private static final Pattern STARTING_PATTERN = Pattern.compile("^Starting in \\d second(s)?");
     private static final Pattern CLASS_PATTERN = Pattern.compile("\\[([ABHMT])]");
@@ -35,7 +36,11 @@ public class RunStartValidator {
 
     private static boolean hasTicked = false;
 
-    public static void init() {
+    public RunStartValidator() {
+        super("Run start validator");
+    }
+
+    public void init() {
         Events.ON_GAME_MESSAGE.register(message -> {
             if (!Location.inDungeon() || Phase.runStarted() || !Dungeons.detectDuplicateClass) return false;
 
@@ -49,16 +54,16 @@ public class RunStartValidator {
             return false;
         });
 
-        Events.ON_LOCATION_CHANGE.register(location -> {
+        Events.ON_LOCATION_CHANGE.register(_ -> {
             if (Location.inDungeon()) {
                 PartyUtil.sendPacket();
-                reset();
+                resetInfo();
             }
 
             return false;
         });
 
-        Events.ON_PLAYER_ENTRY.register(receivedEntry -> {
+        Events.ON_PLAYER_ENTRY.register(_ -> {
             //will only check after the first game message in case of false positives
             if (!hasTicked) return false;
             if (!Location.inDungeon() || Phase.runStarted() || (!Dungeons.detectDuplicateClass && !Dungeons.detectPlayerCount))
@@ -68,7 +73,30 @@ public class RunStartValidator {
         });
     }
 
-    private static boolean validate() {
+    @Override
+    public boolean shouldRender() {
+        return Location.inDungeon() && !Phase.runStarted() && (hasDupeClasses || notEnoughPlayers);
+    }
+
+    @Override
+    public List<HUDCategory> categories() {
+        return List.of(Categories.CLEAR);
+    }
+
+    @Override
+    public boolean enabled() {
+        return (Dungeons.detectDuplicateClass || Dungeons.detectPlayerCount);
+
+    }
+
+    @Override
+    public Component getText() {
+        if (hasDupeClasses) return DUPE_CLASS_TEXT;
+        else if (notEnoughPlayers) return PLAYER_COUNT_TEXT;
+        else return Component.literal("§cSome warning text");
+    }
+
+    private boolean validate() {
         ClientLevel world = Minecraft.getInstance().level;
         LocalPlayer player = Minecraft.getInstance().player;
         if (world == null || player == null) return false;
@@ -81,7 +109,7 @@ public class RunStartValidator {
         return hasDupeClasses || notEnoughPlayers;
     }
 
-    private static void checkDuplicateClasses(HashMap<String, Integer> map) {
+    private void checkDuplicateClasses(HashMap<String, Integer> map) {
         for (Map.Entry<String, Integer> entry : map.entrySet()) {
             String clazz = entry.getKey();
             Integer count = entry.getValue();
@@ -96,7 +124,7 @@ public class RunStartValidator {
         hasDupeClasses = false;
     }
 
-    private static void checkPlayerCount(HashMap<String, Integer> map) {
+    private void checkPlayerCount(HashMap<String, Integer> map) {
         if (!Dungeons.detectPlayerCount) {
             notEnoughPlayers = false;
             return;
@@ -109,13 +137,13 @@ public class RunStartValidator {
         notEnoughPlayers = count < PartyUtil.getPlayerCount();
     }
 
-    private static void reset() {
+    private void resetInfo() {
         hasDupeClasses = false;
         notEnoughPlayers = false;
         hasTicked = false;
     }
 
-    private static HashMap<String, Integer> readScoreBoard(Scoreboard scoreboard) {
+    private HashMap<String, Integer> readScoreBoard(Scoreboard scoreboard) {
         HashMap<String, Integer> map = new HashMap<>();
 
         for (PlayerTeam team : scoreboard.getPlayerTeams()) {
@@ -130,18 +158,4 @@ public class RunStartValidator {
         }
         return map;
     }
-
-    public static boolean display() {
-        return Location.inDungeon() && !Phase.runStarted() && (hasDupeClasses || notEnoughPlayers);
-    }
-
-    public static void render(HUDComponent component, GuiGraphicsExtractor graphics) {
-        if (hasDupeClasses) {
-            RenderUtils.drawCenteredText(graphics, component, DUPE_CLASS_TEXT);
-        } else if (notEnoughPlayers) {
-            RenderUtils.drawCenteredText(graphics, component, PLAYER_COUNT_TEXT);
-        } else {
-            RenderUtils.drawCenteredText(graphics, component, Component.literal("§cSome warning text"));
-        }
-    }
 }
diff --git a/src/main/java/blade/addon/features/dungeon/SecretSpawnTimer.java b/src/main/java/blade/addon/features/dungeon/SecretSpawnTimer.java
index 29e8fe1..7cc55c0 100644
--- a/src/main/java/blade/addon/features/dungeon/SecretSpawnTimer.java
+++ b/src/main/java/blade/addon/features/dungeon/SecretSpawnTimer.java
@@ -2,27 +2,34 @@ package blade.addon.features.dungeon;
 
 import blade.addon.utils.Constants;
 import blade.addon.utils.Location;
+import blade.addon.utils.config.components.Categories;
 import blade.addon.utils.config.values.Dungeons;
 import blade.addon.utils.dungeon.Phase;
 import blade.addon.utils.events.Events;
 import blade.addon.utils.rendering.RenderUtils;
+import config.practical.hud.HUDCategory;
 import config.practical.hud.HUDComponent;
 import net.minecraft.client.Minecraft;
 import net.minecraft.client.gui.GuiGraphicsExtractor;
 import net.minecraft.network.chat.Component;
+import org.jspecify.annotations.NonNull;
 
+import java.util.List;
 import java.util.regex.Matcher;
 import java.util.regex.Pattern;
 
-public class SecretSpawnTimer {
+public class SecretSpawnTimer extends HUDComponent {
 
     private static final Pattern PATTERN = Pattern.compile("^Time Elapsed: ");
     private static final int TICKS_PER_SECOND = 20;
 
     private static int tick = 0;
 
-    public static void init() {
+    public SecretSpawnTimer() {
+        super("Secret spawn timer");
+    }
 
+    public void init() {
         Events.ON_TEAM.register(text -> {
             if (!Location.inDungeon() || !Dungeons.enableSecretSpawnTimer) return false;
             Matcher matcher = PATTERN.matcher(text);
@@ -38,17 +45,38 @@ public class SecretSpawnTimer {
         });
     }
 
-    public static boolean display() {
+    @Override
+    public int getWidth() {
+        return 20;
+    }
+
+    @Override
+    public int getHeight() {
+        return 10;
+    }
+
+    @Override
+    public boolean editable() {
+        return Dungeons.enableSecretSpawnTimer;
+    }
+
+    @Override
+    public boolean shouldRender() {
         return Location.inDungeon() && Dungeons.enableSecretSpawnTimer && !Phase.inBoss() && Phase.runStarted();
     }
 
-    public static void render(HUDComponent component, GuiGraphicsExtractor context) {
-        int x = component.getScaledX();
-        int y = component.getScaledY();
+    @Override
+    public List<HUDCategory> categories() {
+        return List.of(Categories.CLEAR);
+    }
 
-        int color = tick > 10 ? Constants.GREEN : tick > 5 ? Constants.GOLD : Constants.RED;
+    @Override
+    public void render(@NonNull GuiGraphicsExtractor guiGraphicsExtractor) {
+        int x = getScaledX();
+        int y = getScaledY();
 
-        RenderUtils.drawCenteredText(context, Minecraft.getInstance().font, Component.literal(tick + ""), x, y, component.getWidth(), color);
+        int color = tick > 10 ? Constants.GREEN : tick > 5 ? Constants.GOLD : Constants.RED;
+        RenderUtils.drawCenteredText(guiGraphicsExtractor, Minecraft.getInstance().font, Component.literal(tick + ""), x, y, this.getWidth(), color);
 
     }
 }
diff --git a/src/main/java/blade/addon/features/dungeon/WarpCooldown.java b/src/main/java/blade/addon/features/dungeon/WarpCooldown.java
index 657e8ea..5e39c52 100644
--- a/src/main/java/blade/addon/features/dungeon/WarpCooldown.java
+++ b/src/main/java/blade/addon/features/dungeon/WarpCooldown.java
@@ -3,36 +3,77 @@ package blade.addon.features.dungeon;
 import blade.addon.utils.config.values.Dungeons;
 import blade.addon.utils.events.Events;
 import blade.addon.utils.rendering.RenderUtils;
+import config.practical.hud.HUDCategory;
 import config.practical.hud.HUDComponent;
 import net.minecraft.client.gui.GuiGraphicsExtractor;
+import org.jspecify.annotations.NonNull;
 
+import java.util.List;
 import java.util.regex.Matcher;
 import java.util.regex.Pattern;
 
-public class WarpCooldown {
+public class WarpCooldown extends HUDComponent {
 
-    private static final int WARP_COOLDOWN_MS = 30 * 1000;
-    private static final Pattern PATTERN = Pattern.compile("^-*\\n\\[[^]]+] (\\w+) entered (?:MM )?\\w+ Catacombs, Floor (\\w+)!\\n-*$");
+    private static final int INSTANCE_COOLDOWN_MS = 30 * 1000;
+    private static final int WARP_COOLDOWN_MS = 4 * 1000;
+
+    private static final Pattern INSTANCE_PATTERN = Pattern.compile("^-*\\n\\[[^]]+] (\\w+) entered (?:MM )?\\w+ Catacombs, Floor (\\w+)!\\n-*$");
+    private static final Pattern WARP_PATTERN = Pattern.compile("^Sending to server .+");
 
     private static long endTime = 0;
     private static boolean displayCooldown = false;
 
-    public static void init() {
+    public WarpCooldown() {
+        super("Warp cooldown");
+    }
+
+    public void init() {
         Events.ON_GAME_MESSAGE.register(message -> {
             if (!Dungeons.enableWarpCooldown) return false;
 
             if ((endTime - System.currentTimeMillis()) > 0) return false;
 
-            Matcher matcher = PATTERN.matcher(message.getString());
+            Matcher matcher = INSTANCE_PATTERN.matcher(message.getString());
+            if (matcher.find()) {
+                setCooldown(INSTANCE_COOLDOWN_MS);
+                return false;
+            }
+
+            matcher = WARP_PATTERN.matcher(message.getString());
             if (matcher.find()) {
-                endTime = System.currentTimeMillis() + WARP_COOLDOWN_MS;
-                displayCooldown = true;
+                setCooldown(WARP_COOLDOWN_MS);
             }
+
+
             return false;
         });
     }
 
-    public static boolean display()  {
+    private void setCooldown(int duration) {
+        displayCooldown = true;
+        long newEndTime = System.currentTimeMillis() + duration;
+        if (newEndTime < endTime) return;
+        endTime = newEndTime;
+
+    }
+
+    @Override
+    public int getWidth() {
+        return 110;
+    }
+
+    @Override
+    public int getHeight() {
+        return 10;
+    }
+
+    @Override
+    public boolean editable() {
+        return Dungeons.enableWarpCooldown;
+    }
+
+    @Override
+    public boolean shouldRender() {
         if (!Dungeons.enableWarpCooldown || !displayCooldown) return false;
 
         if ((endTime - System.currentTimeMillis()) > 0) {
@@ -43,8 +84,14 @@ public class WarpCooldown {
         }
     }
 
-    public static void render(HUDComponent component, GuiGraphicsExtractor graphics) {
+    @Override
+    public List<HUDCategory> categories() {
+        return null;
+    }
+
+    @Override
+    public void render(@NonNull GuiGraphicsExtractor guiGraphicsExtractor) {
         double timeRemaining = Math.max((endTime - System.currentTimeMillis()) / 1000.0, 0);
-        RenderUtils.drawPrefixedTimer(component, graphics, "Warp Cooldown", timeRemaining);
+        RenderUtils.drawPrefixedTimer(this, guiGraphicsExtractor, "Warp Cooldown", timeRemaining);
     }
 }
diff --git a/src/main/java/blade/addon/features/dungeon/f7/StormLBTimer.java b/src/main/java/blade/addon/features/dungeon/f7/StormLBTimer.java
index c42c3b8..818daec 100644
--- a/src/main/java/blade/addon/features/dungeon/f7/StormLBTimer.java
+++ b/src/main/java/blade/addon/features/dungeon/f7/StormLBTimer.java
@@ -2,28 +2,38 @@ package blade.addon.features.dungeon.f7;
 
 import blade.addon.utils.Constants;
 import blade.addon.utils.Location;
+import blade.addon.utils.config.components.Categories;
 import blade.addon.utils.config.values.Floor7;
 import blade.addon.utils.dungeon.DungeonClass;
 import blade.addon.utils.dungeon.Phase;
 import blade.addon.utils.events.Events;
 import blade.addon.utils.rendering.RenderUtils;
+import config.practical.hud.HUDCategory;
 import config.practical.hud.HUDComponent;
 import net.minecraft.client.gui.GuiGraphicsExtractor;
+import org.jspecify.annotations.NonNull;
 
-public class StormLBTimer {
+import java.util.List;
+
+
+public class StormLBTimer extends HUDComponent {
 
     private static final int BASE_LB_TICK = 20 * 34;
     private static final int DISPLAY_DURATION_TICK = 20 * 5;
 
-    private static int tick = 0;
+    private int tick = 0;
 
-    public static void init() {
+    public StormLBTimer() {
+        super("Storm lb timer");
+    }
+
+    public void init() {
         Events.ON_SERVER_TICK.register(() -> {
             if (Location.inDungeon() && Phase.inP2() && !Phase.stormDead()) tick++;
             return false;
         });
 
-        Events.ON_LOCATION_CHANGE.register(newLocation -> {
+        Events.ON_LOCATION_CHANGE.register(_ -> {
             if (Location.inDungeon()) {
                 tick = 0;
             }
@@ -31,24 +41,45 @@ public class StormLBTimer {
         });
     }
 
-    private static int getCurrentTickTime() {
-        return BASE_LB_TICK + Floor7.lbTickOffset - tick;
+    @Override
+    public int getWidth() {
+        return 30;
     }
 
-    private static boolean validTime() {
-        int deltaTick = getCurrentTickTime();
-        return deltaTick > 0 && deltaTick <= DISPLAY_DURATION_TICK;
+    @Override
+    public int getHeight() {
+        return 10;
+    }
 
+    @Override
+    public boolean editable() {
+        return Floor7.showLBTimer;
     }
 
-    public static boolean display() {
+    @Override
+    public boolean shouldRender() {
         return Floor7.showLBTimer && Location.inDungeon() && Phase.inP2() && !Phase.stormDead() && validTime() && DungeonClass.isClass(DungeonClass.ARCHER);
     }
 
-    public static void render(HUDComponent component, GuiGraphicsExtractor graphics) {
+    @Override
+    public List<HUDCategory> categories() {
+        return List.of(Categories.P2);
+    }
+
+    @Override
+    public void render(@NonNull GuiGraphicsExtractor guiGraphicsExtractor) {
         int deltaTick = getCurrentTickTime();
         double num = deltaTick * Constants.TICK_DURATION;
-        RenderUtils.drawTimer(component, graphics, num, RenderUtils.getStatusColor(60, 30, deltaTick));
+        RenderUtils.drawTimer(this, guiGraphicsExtractor, num, RenderUtils.getStatusColor(60, 30, deltaTick));
+    }
+
+    private int getCurrentTickTime() {
+        return BASE_LB_TICK + Floor7.lbTickOffset - tick;
     }
 
+    private boolean validTime() {
+        int deltaTick = getCurrentTickTime();
+        return deltaTick > 0 && deltaTick <= DISPLAY_DURATION_TICK;
+
+    }
 }
diff --git a/src/main/java/blade/addon/features/dungeon/f7/dragons/DragSpawnTimer.java b/src/main/java/blade/addon/features/dungeon/f7/dragons/DragonSpawn.java
similarity index 83%
rename from src/main/java/blade/addon/features/dungeon/f7/dragons/DragSpawnTimer.java
rename to src/main/java/blade/addon/features/dungeon/f7/dragons/DragonSpawn.java
index fa7f96f..45ecb61 100644
--- a/src/main/java/blade/addon/features/dungeon/f7/dragons/DragSpawnTimer.java
+++ b/src/main/java/blade/addon/features/dungeon/f7/dragons/DragonSpawn.java
@@ -4,15 +4,12 @@ import blade.addon.utils.Misc;
 import blade.addon.utils.config.values.Floor7;
 import blade.addon.utils.debug.Debug;
 import blade.addon.utils.events.Events;
-import blade.addon.utils.rendering.RenderUtils;
-import config.practical.hud.HUDComponent;
-import net.minecraft.client.gui.GuiGraphicsExtractor;
 import net.minecraft.core.particles.ParticleTypes;
 import net.minecraft.network.chat.Component;
 import net.minecraft.network.protocol.game.ClientboundLevelParticlesPacket;
 import net.minecraft.sounds.SoundEvents;
 
-public class DragSpawnTimer {
+public class DragonSpawn {
 
     public enum Team {
         ARCHER_TEAM("Archer team"), BERS_TEAM("Bers team");
@@ -52,8 +49,8 @@ public class DragSpawnTimer {
             return false;
         });
 
-        Events.ON_LOCATION_CHANGE.register(newLocation -> {
-            reset();
+        Events.ON_LOCATION_CHANGE.register(_ -> {
+            resetInfo();
             return false;
         });
     }
@@ -89,17 +86,13 @@ public class DragSpawnTimer {
         return true;
     }
 
-    private static void reset() {
+    private static void resetInfo() {
         hasDoneSplit = false;
         currentDragon = Dragon.NONE;
         tick = 0;
     }
 
-    public static boolean display() {
-        return Floor7.dragSpawnTimers && currentDragon != Dragon.NONE;
-    }
-
-    public static void render(HUDComponent component, GuiGraphicsExtractor graphics) {
-        RenderUtils.drawTimer(component, graphics, tick, currentDragon.color);
+    public static int getTick() {
+        return tick;
     }
 }
diff --git a/src/main/java/blade/addon/features/dungeon/f7/dragons/DragonSpawnTimer.java b/src/main/java/blade/addon/features/dungeon/f7/dragons/DragonSpawnTimer.java
new file mode 100644
index 0000000..2b57c35
--- /dev/null
+++ b/src/main/java/blade/addon/features/dungeon/f7/dragons/DragonSpawnTimer.java
@@ -0,0 +1,48 @@
+package blade.addon.features.dungeon.f7.dragons;
+
+import blade.addon.utils.config.components.Categories;
+import blade.addon.utils.config.values.Floor7;
+import blade.addon.utils.rendering.RenderUtils;
+import config.practical.hud.HUDCategory;
+import config.practical.hud.HUDComponent;
+import net.minecraft.client.gui.GuiGraphicsExtractor;
+import org.jspecify.annotations.NonNull;
+
+import java.util.List;
+
+public class DragonSpawnTimer extends HUDComponent {
+
+    public DragonSpawnTimer() {
+        super("Dragon spawn timer");
+    }
+
+    @Override
+    public int getWidth() {
+        return 30;
+    }
+
+    @Override
+    public int getHeight() {
+        return 10;
+    }
+
+    @Override
+    public boolean editable() {
+        return Floor7.dragSpawnTimers;
+    }
+
+    @Override
+    public boolean shouldRender() {
+        return Floor7.dragSpawnTimers && DragonSpawn.currentDragon != Dragon.NONE;
+    }
+
+    @Override
+    public List<HUDCategory> categories() {
+        return List.of(Categories.P5);
+    }
+
+    @Override
+    public void render(@NonNull GuiGraphicsExtractor guiGraphicsExtractor) {
+        RenderUtils.drawTimer(this, guiGraphicsExtractor, DragonSpawn.getTick(), DragonSpawn.currentDragon.color);
+    }
+}
diff --git a/src/main/java/blade/addon/features/dungeon/f7/dragons/DragonTracer.java b/src/main/java/blade/addon/features/dungeon/f7/dragons/DragonTracer.java
index f5a7155..579e68f 100644
--- a/src/main/java/blade/addon/features/dungeon/f7/dragons/DragonTracer.java
+++ b/src/main/java/blade/addon/features/dungeon/f7/dragons/DragonTracer.java
@@ -16,8 +16,8 @@ public class DragonTracer {
     private static void render(LevelRenderContext context, PoseStack matrixStack, VertexConsumer consumer) {
         if (!Floor7.dragonTracer) return;
 
-        Dragon dragon = DragSpawnTimer.currentDragon;
-        if (dragon == null || dragon == Dragon.NONE || !DragSpawnTimer.hasDoneSplit) return;
+        Dragon dragon = DragonSpawn.currentDragon;
+        if (dragon == null || dragon == Dragon.NONE || !DragonSpawn.hasDoneSplit) return;
 
         RenderUtils.renderLineTo(context, dragon.spawnPos, dragon.color);
     }
diff --git a/src/main/java/blade/addon/features/dungeon/f7/invincibility/InvincibilityDisplay.java b/src/main/java/blade/addon/features/dungeon/f7/invincibility/InvincibilityDisplay.java
new file mode 100644
index 0000000..26a2dd7
--- /dev/null
+++ b/src/main/java/blade/addon/features/dungeon/f7/invincibility/InvincibilityDisplay.java
@@ -0,0 +1,136 @@
+package blade.addon.features.dungeon.f7.invincibility;
+
+import blade.addon.utils.Constants;
+import blade.addon.utils.Location;
+import blade.addon.utils.config.components.Categories;
+import blade.addon.utils.config.values.Dungeons;
+import blade.addon.utils.dungeon.Phase;
+import config.practical.hud.HUDCategory;
+import config.practical.hud.HUDComponent;
+import net.minecraft.client.Minecraft;
+import net.minecraft.client.gui.GuiGraphicsExtractor;
+import net.minecraft.client.renderer.RenderPipelines;
+import net.minecraft.network.chat.Component;
+import net.minecraft.resources.Identifier;
+import org.jspecify.annotations.NonNull;
+
+import java.util.List;
+
+public class InvincibilityDisplay extends HUDComponent {
+
+    private static final Identifier BONZO_SPRITE = Identifier.fromNamespaceAndPath(Constants.NAMESPACE, "bonzo-mask");
+    private static final Identifier SPIRIT_SPRITE = Identifier.fromNamespaceAndPath(Constants.NAMESPACE, "spirit-mask");
+    private static final Identifier PHOENIX_SPRITE = Identifier.fromNamespaceAndPath(Constants.NAMESPACE, "phoenix");
+
+    private static final int TEXT_HEIGHT = 9;
+    private static final int SPRITE_SIZE = TEXT_HEIGHT;
+
+
+    public InvincibilityDisplay() {
+        super("Invincibility timer");
+    }
+
+    @Override
+    public int getWidth() {
+        return 110;
+    }
+
+    @Override
+    public int getHeight() {
+        return 28;
+    }
+
+    @Override
+    public boolean editable() {
+        return Dungeons.displayInvincibilityTimer;
+    }
+
+    @Override
+    public boolean shouldRender() {
+        if (!Dungeons.displayInvincibilityTimer || !Location.inDungeon()) {
+            return false;
+        }
+
+        switch (Dungeons.displayWhen) {
+            case BOSS_ONLY -> {
+                return Phase.inBoss();
+            }
+            case P3_ONLY -> {
+                return Phase.inP3();
+            }
+            case ALWAYS -> {
+                return true;
+            }
+            case USEFUL_PHASES -> {
+                return Phase.inP3() || Phase.inP2();
+            }
+            case null, default -> {
+                return false;
+            }
+        }
+    }
+
+    @Override
+    public List<HUDCategory> categories() {
+         switch (Dungeons.displayWhen) {
+            case BOSS_ONLY -> {
+                return List.of(Categories.P1, Categories.P2, Categories.P3, Categories.P4, Categories.P5);
+            }
+            case P3_ONLY -> {
+                return List.of(Categories.P3);
+            }
+            case ALWAYS -> {
+                return null;
+            }
+            case USEFUL_PHASES -> {
+                return List.of(Categories.P2, Categories.P3);
+            }
+        }
+
+        return null;
+    }
+
+    private static Component formatTimer(int ticks) {
+        return Component.literal("§8(§7" + Constants.DECIMAL_FORMAT.format(ticks * Constants.TICK_DURATION) + "§8)");
+    }
+
+    private static Component getText(int ticks, boolean isOn, String string) {
+        if (ticks > 0) {
+            return Component.literal("§c" + string).append(formatTimer(ticks));
+        } else {
+            String color = isOn ? "§e" : "§a";
+            return Component.literal(color + string);
+        }
+    }
+
+    private static void drawSprite(GuiGraphicsExtractor context, Identifier identifier, int x, int y, boolean isOn, int ticks, Component timerText) {
+        int color = (ticks > 0 ? Constants.RED : isOn ? Constants.YELLOW : Constants.GREEN);
+        context.fill(x, y, x + SPRITE_SIZE, y + SPRITE_SIZE, color);
+        context.blitSprite(RenderPipelines.GUI_TEXTURED, identifier, x, y, SPRITE_SIZE, SPRITE_SIZE, 0xffffffff);
+        if (ticks > 0) {
+            context.text(Minecraft.getInstance().font, timerText, x + TEXT_HEIGHT * 2, y, 0xffffffff, true);
+        }
+
+    }
+
+    @Override
+    public void render(@NonNull GuiGraphicsExtractor guiGraphicsExtractor) {
+        int x = getScaledX();
+        int y = getScaledY();
+
+        if (Dungeons.useSprites) {
+            drawSprite(guiGraphicsExtractor, BONZO_SPRITE, x, y, InvincibilityTimer.bonzoMaskOn, InvincibilityTimer.bonzoMaskTicks, formatTimer(InvincibilityTimer.bonzoMaskTicks));
+            drawSprite(guiGraphicsExtractor, SPIRIT_SPRITE, x, y + SPRITE_SIZE + 1, InvincibilityTimer.spiritMaskOn, InvincibilityTimer.spiritMaskTicks, formatTimer(InvincibilityTimer.spiritMaskTicks));
+            drawSprite(guiGraphicsExtractor, PHOENIX_SPRITE, x, y + (SPRITE_SIZE + 1) * 2, InvincibilityTimer.phoenixOn, InvincibilityTimer.phoenixTicks, formatTimer(InvincibilityTimer.phoenixTicks));
+        } else {
+            if (Dungeons.removeMaskPart) {
+                guiGraphicsExtractor.text(Minecraft.getInstance().font, getText(InvincibilityTimer.bonzoMaskTicks, InvincibilityTimer.bonzoMaskOn, "Bonzo "), x, y, 0xffffffff, true);
+                guiGraphicsExtractor.text(Minecraft.getInstance().font, getText(InvincibilityTimer.spiritMaskTicks, InvincibilityTimer.spiritMaskOn, "Spirit "), x, y + TEXT_HEIGHT, 0xffffffff, true);
+            } else {
+                guiGraphicsExtractor.text(Minecraft.getInstance().font, getText(InvincibilityTimer.bonzoMaskTicks, InvincibilityTimer.bonzoMaskOn, "Bonzo's Mask "), x, y, 0xffffffff, true);
+                guiGraphicsExtractor.text(Minecraft.getInstance().font, getText(InvincibilityTimer.spiritMaskTicks, InvincibilityTimer.spiritMaskOn, "Spirit Mask "), x, y + TEXT_HEIGHT, 0xffffffff, true);
+            }
+            guiGraphicsExtractor.text(Minecraft.getInstance().font, getText(InvincibilityTimer.phoenixTicks, InvincibilityTimer.phoenixOn, "Phoenix "), x, y + TEXT_HEIGHT * 2, 0xffffffff, true);
+        }
+    }
+}
diff --git a/src/main/java/blade/addon/features/dungeon/f7/invincibility/InvincibilityDuration.java b/src/main/java/blade/addon/features/dungeon/f7/invincibility/InvincibilityDuration.java
index b136cdc..d77e753 100644
--- a/src/main/java/blade/addon/features/dungeon/f7/invincibility/InvincibilityDuration.java
+++ b/src/main/java/blade/addon/features/dungeon/f7/invincibility/InvincibilityDuration.java
@@ -4,35 +4,66 @@ import blade.addon.utils.Misc;
 import blade.addon.utils.config.values.Dungeons;
 import blade.addon.utils.events.Events;
 import blade.addon.utils.rendering.RenderUtils;
+import config.practical.hud.HUDCategory;
 import config.practical.hud.HUDComponent;
 import net.minecraft.client.gui.GuiGraphicsExtractor;
+import org.jspecify.annotations.NonNull;
 
-public class InvincibilityDuration {
+import java.util.List;
+
+public class InvincibilityDuration extends HUDComponent {
 
     private static final int MAX_DURATION = 3 * 20;
-    private static int ticks = 0;
+    private int ticks = 0;
+
+    public InvincibilityDuration() {
+        super("Invincibility duration");
+    }
 
-    public static void init() {
+    public void init() {
         Events.ON_SERVER_TICK.register(() -> {
             ticks = Math.max(0, ticks - 1);
             return false;
         });
     }
 
-    public static void proc() {
-        ticks = MAX_DURATION;
-        Misc.sendSound(Dungeons.invincibilitySound);
+    @Override
+    public int getWidth() {
+        return 30;
+    }
+
+
…… diff 过长已截断 ……
- fixed a crash when both the nametag and outline was active on a teammate (26.1.2 only issue)Gabriel Ellnebrand · 2026-08-03 · a1b67ee
查看变更
src/main/java/blade/addon/features/highlight/TeammateHighlight.java | 40 ++++++++++++++++++++++++++++------------
 src/main/java/blade/addon/utils/Location.java                       |  7 ++++---
 src/main/java/blade/addon/utils/rendering/RenderLayers.java         |  2 ++
 src/main/java/blade/addon/utils/rendering/RenderPipelines.java      |  5 +++++
 src/main/java/blade/addon/utils/rendering/RenderingEvents.java      |  2 ++
 5 files changed, 41 insertions(+), 15 deletions(-)
diff --git a/src/main/java/blade/addon/features/highlight/TeammateHighlight.java b/src/main/java/blade/addon/features/highlight/TeammateHighlight.java
index 83162a0..f22548a 100644
--- a/src/main/java/blade/addon/features/highlight/TeammateHighlight.java
+++ b/src/main/java/blade/addon/features/highlight/TeammateHighlight.java
@@ -44,29 +44,45 @@ public class TeammateHighlight {
         ClientTickEvents.END_CLIENT_TICK.register(client -> teammates.removeIf(Entity::isRemoved));
 
         RenderingEvents.LINE_NO_DEPTH.register(TeammateHighlight::renderOutline);
+        RenderingEvents.TEXT.register(TeammateHighlight::renderText);
+    }
+
+    private static boolean shouldRender(Player player) {
+        if (Dungeons.dontHighlightHiddenTeammates && HidePlayers.shouldHidePlayers(player)) return false;
+        if (Dungeons.dontHighlightVisibleTeammates && !HidePlayers.shouldHidePlayers(player)) return false;
+        return true;
     }
 
     private static void renderOutline(LevelRenderContext context, PoseStack matrixStack, VertexConsumer consumer) {
-        if (!Dungeons.highlightTeammates && !Dungeons.renderClassName) return;
+        if (!Dungeons.highlightTeammates) return;
 
         teammates.forEach(player -> {
-            if (Dungeons.dontHighlightHiddenTeammates && HidePlayers.shouldHidePlayers(player)) return;
-            if (Dungeons.dontHighlightVisibleTeammates && !HidePlayers.shouldHidePlayers(player)) return;
+            if (!shouldRender(player)) return;
             DungeonClass clazz = DungeonClass.getClass(player);
             if (clazz == null) return;
 
             int color = DungeonClass.getColor(clazz);
 
-            if (Dungeons.highlightTeammates) {
-                float[] rgba = RenderUtils.toFloats(color);
-                RenderUtils.renderOutlinedBox(matrixStack, consumer,EntityUtil.getBox(player), rgba);
-            }
+            float[] rgba = RenderUtils.toFloats(color);
+            RenderUtils.renderOutlinedBox(matrixStack, consumer, EntityUtil.getBox(player), rgba);
+
+        });
+    }
+
+    private static void renderText(LevelRenderContext context, PoseStack matrixStack, VertexConsumer consumer) {
+        if (!Dungeons.renderClassName) return;
+
+        teammates.forEach(player -> {
+            if (!shouldRender(player)) return;
+            DungeonClass clazz = DungeonClass.getClass(player);
+            if (clazz == null) return;
+
+            int color = DungeonClass.getColor(clazz);
+
+            Component text = Component.literal(player.getName().getString()).withColor(color).append(Component.literal(" [" + DungeonClass.getChar(clazz) + "]").withColor(Constants.YELLOW));
+            Vec3 pos = EntityUtil.getLerpedPos(player);
+            RenderUtils.renderText(context, matrixStack, text, pos.x(), pos.y() + 2.75, pos.z(), 2);
 
-            if (Dungeons.renderClassName) {
-                Component text = Component.literal(player.getName().getString()).withColor(color).append(Component.literal(" [" + DungeonClass.getChar(clazz) + "]").withColor(Constants.YELLOW));
-                Vec3 pos = EntityUtil.getLerpedPos(player);
-                RenderUtils.renderText(context, matrixStack, text, pos.x(), pos.y() + 2.75, pos.z(), 2);
-            }
         });
     }
 
diff --git a/src/main/java/blade/addon/utils/Location.java b/src/main/java/blade/addon/utils/Location.java
index 36520ce..b663a98 100644
--- a/src/main/java/blade/addon/utils/Location.java
+++ b/src/main/java/blade/addon/utils/Location.java
@@ -29,8 +29,9 @@ public enum Location {
     JERRYS_WORKSHOP("Jerry's Workshop"),
     MINESHAFT("Mineshaft"),
     DARK_AUCTION("Dark Auction"),
-    KUUDRA("Kuudra"),;
-
+    KUUDRA("Kuudra"),
+    TORRHUS_CANYON("Torrhus Canyon"),
+    SAFARI("Safari");
     final String name;
 
     Location(String name) {
@@ -51,7 +52,7 @@ public enum Location {
                 ServerType serverType = packet.getServerType().get();
                 inSkyblock = serverType.getName().equals("SkyBlock");
             }
-
+            Debug.sendDebugMessage(Component.literal("Parsing location: " + locationName));
             changeLocation(getLocation(locationName));
         }));
 
diff --git a/src/main/java/blade/addon/utils/rendering/RenderLayers.java b/src/main/java/blade/addon/utils/rendering/RenderLayers.java
index 68ecf08..245d939 100644
--- a/src/main/java/blade/addon/utils/rendering/RenderLayers.java
+++ b/src/main/java/blade/addon/utils/rendering/RenderLayers.java
@@ -25,5 +25,7 @@ public class RenderLayers {
     public static final RenderType LINE_NO_DEPTH =
             RenderType.create("line-no-depth-layer", RenderSetup.builder(RenderPipelines.LINE_NO_DEPTH).setOutline(RenderSetup.OutlineProperty.IS_OUTLINE).setLayeringTransform(LayeringTransform.VIEW_OFFSET_Z_LAYERING).createRenderSetup());
 
+    public static final RenderType TEXT =
+            RenderType.create("text-layer", RenderSetup.builder(RenderPipelines.TEXT).setOutline(RenderSetup.OutlineProperty.IS_OUTLINE).setLayeringTransform(LayeringTransform.VIEW_OFFSET_Z_LAYERING).createRenderSetup());
 
 }
diff --git a/src/main/java/blade/addon/utils/rendering/RenderPipelines.java b/src/main/java/blade/addon/utils/rendering/RenderPipelines.java
index cc01f29..d082cea 100644
--- a/src/main/java/blade/addon/utils/rendering/RenderPipelines.java
+++ b/src/main/java/blade/addon/utils/rendering/RenderPipelines.java
@@ -48,4 +48,9 @@ public class RenderPipelines {
             .withDepthStencilState(new DepthStencilState(CompareOp.ALWAYS_PASS, false))
             .build());
 
+    public static final RenderPipeline TEXT = net.minecraft.client.renderer.RenderPipelines.register(RenderPipeline.builder(net.minecraft.client.renderer.RenderPipelines.LINES_SNIPPET)
+            .withLocation(Identifier.fromNamespaceAndPath(blade.addon.utils.Constants.NAMESPACE, "text-pipe"))
+            .withVertexFormat(DefaultVertexFormat.POSITION_COLOR_NORMAL_LINE_WIDTH, VertexFormat.Mode.LINES)
+            .withDepthStencilState(new DepthStencilState(CompareOp.ALWAYS_PASS, false))
+            .build());
 }
diff --git a/src/main/java/blade/addon/utils/rendering/RenderingEvents.java b/src/main/java/blade/addon/utils/rendering/RenderingEvents.java
index d980679..b079839 100644
--- a/src/main/java/blade/addon/utils/rendering/RenderingEvents.java
+++ b/src/main/java/blade/addon/utils/rendering/RenderingEvents.java
@@ -10,6 +10,7 @@ public class RenderingEvents {
     public static RenderHandler LINE = new RenderHandler(RenderLayers.LINE);
     public static RenderHandler FILLED_NO_DEPTH = new RenderHandler(RenderLayers.FILLED_NO_DEPTH);
     public static RenderHandler LINE_NO_DEPTH = new RenderHandler(RenderLayers.LINE_NO_DEPTH);
+    public static RenderHandler TEXT = new RenderHandler(RenderLayers.TEXT);
 
 
     public static void init() {
@@ -19,5 +20,6 @@ public class RenderingEvents {
         LevelRenderEvents.AFTER_TRANSLUCENT_FEATURES.register(LINE::init);
         LevelRenderEvents.AFTER_TRANSLUCENT_FEATURES.register(FILLED_NO_DEPTH::init);
         LevelRenderEvents.AFTER_TRANSLUCENT_FEATURES.register(LINE_NO_DEPTH::init);
+        LevelRenderEvents.AFTER_TRANSLUCENT_FEATURES.register(TEXT::init);
     }
 }
- added the forgotten button for math formattingGabriel Ellnebrand · 2026-07-21 · 71d88d7
查看变更
src/main/java/blade/addon/utils/config/Config.java | 1 +
 1 file changed, 1 insertion(+)
diff --git a/src/main/java/blade/addon/utils/config/Config.java b/src/main/java/blade/addon/utils/config/Config.java
index 1b86466..4a30fce 100644
--- a/src/main/java/blade/addon/utils/config/Config.java
+++ b/src/main/java/blade/addon/utils/config/Config.java
@@ -331,6 +331,7 @@ public class Config {
         extra.add(new ConfigBool(Component.literal("Display selected arrow"), () -> ExtraOptions.displayCurrentArrow, bool -> ExtraOptions.displayCurrentArrow = bool));
         extra.add(new ConfigBool(Component.literal("Title on arrow swap"), () -> ExtraOptions.arrowSwapNotification, bool -> ExtraOptions.arrowSwapNotification = bool));
         extra.add(new ConfigBool(Component.literal("Toggleable searchbar (ctrl + f)"), () -> ExtraOptions.toggleableSearchBar, bool -> ExtraOptions.toggleableSearchBar = bool));
+        extra.add(new ConfigBool(Component.literal("Format math results"), () -> ExtraOptions.formatMathResult, bool -> ExtraOptions.formatMathResult = bool));
         extra.add(new ConfigColor(Component.literal("Searchbar miss color"), () -> ExtraOptions.searchbarMissColor, color -> ExtraOptions.searchbarMissColor = color, "search-bar-miss-color", true));
         extra.add(new ConfigBool(Component.literal("Disable protect item (resets on launch)"), () -> ProtectItem.stopProtectItem, bool -> ProtectItem.stopProtectItem = bool));
         extra.add(new ConfigBool(Component.literal("Display reaper duration"), () -> ExtraOptions.enableReaperDisplay, bool -> ExtraOptions.enableReaperDisplay = bool));
- changed candy tooltipGabriel Ellnebrand · 2026-07-21 · 6aae1da
查看变更
src/main/java/blade/addon/features/item/ToolTip.java | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/main/java/blade/addon/features/item/ToolTip.java b/src/main/java/blade/addon/features/item/ToolTip.java
index 59cd8dc..4ae7d5c 100644
--- a/src/main/java/blade/addon/features/item/ToolTip.java
+++ b/src/main/java/blade/addon/features/item/ToolTip.java
@@ -72,7 +72,7 @@ public class ToolTip {
         String candies = matcher.group(1);
         if (candies.equals("0")) return;
 
-        lines.add(Component.literal("§aCandies : (" + candies + "/10)"));
+        lines.add(Component.literal("§aCandies: (" + candies + "/10)"));
 
 
     }
- added support for searching multiple items at a time (use , between each item) - replaced built in fabric msg received events - added item quality in tooltip - added pet candy in tooltip - fixed pet training breaking the pet displayGabriel Ellnebrand · 2026-07-21 · fc2f873
查看变更
src/main/java/blade/addon/Bladeaddons.java                  |  1 +
 src/main/java/blade/addon/features/dungeon/AutoRequeue.java |  8 +++++---
 src/main/java/blade/addon/features/item/ToolTip.java        | 80 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 src/main/java/blade/addon/features/other/SearchBar.java     | 40 ++++++++++++++++++++++++++++++++++------
 src/main/java/blade/addon/features/other/SelectedPet.java   |  3 +++
 src/main/java/blade/addon/utils/config/Config.java          |  6 ++++++
 src/main/java/blade/addon/utils/config/values/Visual.java   |  6 ++++++
 src/main/java/blade/addon/utils/data/ItemUtil.java          |  7 +++++++
 src/main/java/blade/addon/utils/dungeon/DungeonClass.java   |  7 ++++---
 src/main/java/blade/addon/utils/events/CustomEvents.java    | 19 +++++++++++--------
 10 files changed, 157 insertions(+), 20 deletions(-)
diff --git a/src/main/java/blade/addon/Bladeaddons.java b/src/main/java/blade/addon/Bladeaddons.java
index 435b298..d84fcd2 100644
--- a/src/main/java/blade/addon/Bladeaddons.java
+++ b/src/main/java/blade/addon/Bladeaddons.java
@@ -133,5 +133,6 @@ public class Bladeaddons implements ModInitializer {
         StormLBTimer.init();
         DeathMessage.init();
         BlockPetMenu.init();
+        ToolTip.init();
     }
 }
\ No newline at end of file
diff --git a/src/main/java/blade/addon/features/dungeon/AutoRequeue.java b/src/main/java/blade/addon/features/dungeon/AutoRequeue.java
index cf5ed8e..91a6fcb 100644
--- a/src/main/java/blade/addon/features/dungeon/AutoRequeue.java
+++ b/src/main/java/blade/addon/features/dungeon/AutoRequeue.java
@@ -4,8 +4,8 @@ import blade.addon.utils.Location;
 import blade.addon.utils.Misc;
 import blade.addon.utils.config.values.Dungeons;
 import blade.addon.utils.events.Events;
-import net.fabricmc.fabric.api.client.message.v1.ClientReceiveMessageEvents;
 import net.minecraft.network.chat.Component;
+
 import java.util.HashMap;
 import java.util.regex.Matcher;
 import java.util.regex.Pattern;
@@ -35,13 +35,15 @@ public class AutoRequeue {
             return false;
         });
 
-        ClientReceiveMessageEvents.GAME.register((message, overlay) -> {
-            if (!Location.inDungeon() && !Dungeons.enableAutoRequeue) return;
+        Events.ON_GAME_MESSAGE.register(message -> {
+            if (!Location.inDungeon() && !Dungeons.enableAutoRequeue) return false;
             String string = message.getString();
             Matcher matcher = LEFT_PATTERN.matcher(string);
             if (matcher.find()) {
                 someoneLeft = true;
             }
+
+            return false;
         });
 
         Events.ON_RUN_END.register(() -> {
diff --git a/src/main/java/blade/addon/features/item/ToolTip.java b/src/main/java/blade/addon/features/item/ToolTip.java
new file mode 100644
index 0000000..59cd8dc
--- /dev/null
+++ b/src/main/java/blade/addon/features/item/ToolTip.java
@@ -0,0 +1,80 @@
+package blade.addon.features.item;
+
+import blade.addon.utils.config.values.Visual;
+import blade.addon.utils.data.ItemUtil;
+import net.fabricmc.fabric.api.client.item.v1.ItemTooltipCallback;
+import net.minecraft.nbt.CompoundTag;
+import net.minecraft.network.chat.Component;
+
+import java.util.List;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+public class ToolTip {
+
+    private static final Pattern PATTERN = Pattern.compile("\"candyUsed\":(\\d+),");
+
+    public static void init() {
+        ItemTooltipCallback.EVENT.register((item, tooltipContext, tooltipType, lines) -> {
+
+            CompoundTag nbt = ItemUtil.getNbt(item);
+            if (nbt == null) return;
+            attemptItemQuality(nbt, lines);
+            attemptCandy(nbt, lines);
+
+        });
+    }
+
+
+    private static void attemptItemQuality(CompoundTag nbt, List<Component> lines) {
+        if (!Visual.itemQuality) return;
+        int statboost = nbt.getIntOr("baseStatBoostPercentage", -1);
+        if (statboost == -1) return;
+
+        int tier = nbt.getIntOr("item_tier", -1);
+        if (tier == -1) return;
+
+        String statBoost = "§4Stat boost: " + statboost + "/50";
+        String reqString = "req: " + getFloor(tier);
+
+        lines.add(Component.literal(statBoost + " " + reqString));
+
+    }
+
+    private static String getFloor(int tier) {
+        return switch (tier) {
+            case 0 -> "E";
+            case 1 -> "F1";
+            case 2 -> "F2";
+            case 3 -> "F3";
+            case 4 -> "F4";
+            case 5 -> "F5";
+            case 6 -> "F6";
+            case 7 -> "F7";
+            case 8 -> "M5";
+            case 9 -> "M6";
+            case 10 -> "M7";
+            default -> "?";
+        };
+    }
+
+
+    private static void attemptCandy(CompoundTag nbt, List<Component> lines) {
+        if (!Visual.petCandy) return;
+        if (!nbt.getStringOr("id", "").equals("PET")) return;
+
+        String petInfo = nbt.getStringOr("petInfo", null);
+        if (petInfo == null) return;
+
+        Matcher matcher = PATTERN.matcher(petInfo);
+        if (!matcher.find()) return;
+
+        String candies = matcher.group(1);
+        if (candies.equals("0")) return;
+
+        lines.add(Component.literal("§aCandies : (" + candies + "/10)"));
+
+
+    }
+
+}
diff --git a/src/main/java/blade/addon/features/other/SearchBar.java b/src/main/java/blade/addon/features/other/SearchBar.java
index 0322b3a..4733f1f 100644
--- a/src/main/java/blade/addon/features/other/SearchBar.java
+++ b/src/main/java/blade/addon/features/other/SearchBar.java
@@ -17,19 +17,27 @@ import net.minecraft.network.chat.Component;
 import net.minecraft.world.item.ItemStack;
 import org.lwjgl.glfw.GLFW;
 
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.CopyOnWriteArrayList;
+
 public class SearchBar {
 
     private static final int SEARCH_Y = 20;
     private static final int SEARCH_WIDTH = 150;
     private static final int SEARCH_HEIGHT = 20;
+
     private static EditBox searchBar;
     private static boolean shouldDisplay = false;
-    private static String searchTerm = "";
+
     private static double parsedValue = Double.NaN;
+    private static String writtenString = "";
+    private static CopyOnWriteArrayList<String> searchTerms = new CopyOnWriteArrayList<>();
+
+    private static final ConcurrentHashMap<ItemStack, Boolean> parsedData = new ConcurrentHashMap<>();
 
     public static void init() {
         DrawEvents.INVENTORY_SLOT_AFTER.register((context, item, x, y) -> {
-            if (shouldDisplay() && !searchTerm.isEmpty() && ExtraOptions.toggleableSearchBar && Double.isNaN(parsedValue)) {
+            if (shouldDisplay() && !writtenString.isEmpty() && ExtraOptions.toggleableSearchBar && Double.isNaN(parsedValue)) {
                 if (!matches(item)) {
                     context.fill(x, y, x + 16, y + 16, ExtraOptions.searchbarMissColor);
                 }
@@ -39,10 +47,18 @@ public class SearchBar {
     }
 
     private static boolean matches(ItemStack item) {
+        if (parsedData.containsKey(item)) return parsedData.get(item);
+
         String name = item.getHoverName().getString().toLowerCase();
         if (name.equals("air")) return false;
 
-        return (name.contains(searchTerm) || ItemUtil.containsIgnoreCaseLore(item, searchTerm));
+        for (String term : searchTerms) {
+            if (name.contains(term) || ItemUtil.containsIgnoreCaseLore(item, term)) {
+                return true;
+            }
+        }
+
+        return false;
     }
 
     public static void render(GuiGraphicsExtractor context, int mouseX, int mouseY, float deltaTicks) {
@@ -54,7 +70,7 @@ public class SearchBar {
             Font textRenderer = Minecraft.getInstance().font;
             if (textRenderer == null) return;
 
-            int textX = searchBar.getX() + textRenderer.width(searchTerm) + 4;
+            int textX = searchBar.getX() + textRenderer.width(writtenString) + 4;
             int textY = searchBar.getY() + (searchBar.getHeight() - 8) / 2;
             context.text(textRenderer, expression, textX, textY, 0xffffffff, true);
         }
@@ -106,9 +122,21 @@ public class SearchBar {
         if (window == null || textRenderer == null) return false;
 
         searchBar = new EditBox(textRenderer, (window.getGuiScaledWidth() - SEARCH_WIDTH) / 2, SEARCH_Y, SEARCH_WIDTH, SEARCH_HEIGHT, Component.literal(""));
+        searchBar.setMaxLength(100);
         searchBar.setResponder(string -> {
-            searchTerm = string.toLowerCase();
-            parsedValue = MathParser.parseExpression(searchTerm);
+
+
+            writtenString = string.toLowerCase();
+            parsedValue = MathParser.parseExpression(writtenString);
+            if (Double.isNaN(parsedValue)) {
+                parsedData.clear();
+            }
+
+            String[] terms = writtenString.split(",");
+            searchTerms.clear();
+            for (String term: terms) {
+                searchTerms.add(term.strip());
+            }
         });
         return true;
     }
diff --git a/src/main/java/blade/addon/features/other/SelectedPet.java b/src/main/java/blade/addon/features/other/SelectedPet.java
index a64fd6e..2fc9d72 100644
--- a/src/main/java/blade/addon/features/other/SelectedPet.java
+++ b/src/main/java/blade/addon/features/other/SelectedPet.java
@@ -95,6 +95,9 @@ public class SelectedPet {
                 return false;
             }
 
+            int correctionCheck = string.indexOf("[");
+            if (correctionCheck != 1) return false;
+
             Matcher matcher = TAB_PET_PATTERN.matcher(string);
             if (matcher.find()) {
                 String petName = matcher.group(1).replace(" ✦", "");
diff --git a/src/main/java/blade/addon/utils/config/Config.java b/src/main/java/blade/addon/utils/config/Config.java
index 55301d3..1b86466 100644
--- a/src/main/java/blade/addon/utils/config/Config.java
+++ b/src/main/java/blade/addon/utils/config/Config.java
@@ -413,6 +413,12 @@ public class Config {
         visual.add(new ConfigBool(Component.literal("1.8.9 like fishing bobber"), () -> Visual.oldFishingRod, bool -> Visual.oldFishingRod = bool));
         visual.add(new ConfigBool(Component.literal("Stop shovel swing when flattening dirt"), () -> Visual.stopShovelFlattening, bool -> Visual.stopShovelFlattening = bool));
         visual.add(new ConfigBool(Component.literal("Stop swinging when throwing a pearl"), () -> Visual.stopPearlSwing, bool -> Visual.stopPearlSwing = bool));
+
+        ConfigSection toolTip = new ConfigSection(Component.literal("ToolTips"));
+        toolTip.add(new ConfigBool(Component.literal("Item quality"), () -> Visual.itemQuality, bool -> Visual.itemQuality = bool));
+        toolTip.add(new ConfigBool(Component.literal("Pet candy"), () -> Visual.petCandy, bool -> Visual.petCandy = bool));
+        visual.add(toolTip);
+
         return visual;
     }
 
diff --git a/src/main/java/blade/addon/utils/config/values/Visual.java b/src/main/java/blade/addon/utils/config/values/Visual.java
index 47238bd..1d7b0f2 100644
--- a/src/main/java/blade/addon/utils/config/values/Visual.java
+++ b/src/main/java/blade/addon/utils/config/values/Visual.java
@@ -55,4 +55,10 @@ public class Visual {
     @ConfigValue
     public static boolean circularRarityBackground = false;
 
+    @ConfigValue
+    public static boolean itemQuality = false;
+
+    @ConfigValue
+    public static boolean petCandy = false;
+
 }
diff --git a/src/main/java/blade/addon/utils/data/ItemUtil.java b/src/main/java/blade/addon/utils/data/ItemUtil.java
index 25b5bf8..568c9ce 100644
--- a/src/main/java/blade/addon/utils/data/ItemUtil.java
+++ b/src/main/java/blade/addon/utils/data/ItemUtil.java
@@ -30,6 +30,13 @@ public class ItemUtil {
         return compound.getStringOr("uuid", null);
     }
 
+    public static CompoundTag getNbt(ItemStack item) {
+        CustomData nbt = item.get(DataComponents.CUSTOM_DATA);
+        if (nbt == null) return null;
+
+        return nbt.copyTag();
+    }
+
     public static boolean isHolding(String name) {
         LocalPlayer player = Minecraft.getInstance().player;
         if (player == null) return false;
diff --git a/src/main/java/blade/addon/utils/dungeon/DungeonClass.java b/src/main/java/blade/addon/utils/dungeon/DungeonClass.java
index 6f321ac..7a95e86 100644
--- a/src/main/java/blade/addon/utils/dungeon/DungeonClass.java
+++ b/src/main/java/blade/addon/utils/dungeon/DungeonClass.java
@@ -6,7 +6,6 @@ import blade.addon.utils.config.values.Dungeons;
 import blade.addon.utils.config.values.Floor7;
 import blade.addon.utils.data.EntityUtil;
 import blade.addon.utils.events.Events;
-import net.fabricmc.fabric.api.client.message.v1.ClientReceiveMessageEvents;
 import net.minecraft.network.chat.Component;
 import net.minecraft.world.entity.player.Player;
 
@@ -38,14 +37,16 @@ public enum DungeonClass {
             return false;
         });
 
-        ClientReceiveMessageEvents.GAME.register((message, overlay) -> {
-            if (!Phase.runStarted() && currentClass != null) return;
+        Events.ON_GAME_MESSAGE.register(message -> {
+            if (!Phase.runStarted() && currentClass != null) return false;
 
             Matcher matcher = PATTERN.matcher(message.getString());
             if (matcher.find()) {
                 String dungeonClass = matcher.group(1);
                 currentClass = parseClass(dungeonClass);
             }
+
+            return false;
         });
 
         Events.ON_PLAYER_ENTRY.register(receivedEntry -> {
diff --git a/src/main/java/blade/addon/utils/events/CustomEvents.java b/src/main/java/blade/addon/utils/events/CustomEvents.java
index 7120ce9..c07586e 100644
--- a/src/main/java/blade/addon/utils/events/CustomEvents.java
+++ b/src/main/java/blade/addon/utils/events/CustomEvents.java
@@ -1,8 +1,7 @@
 package blade.addon.utils.events;
 
-import blade.addon.utils.debug.Debug;
 import blade.addon.utils.Location;
-import net.fabricmc.fabric.api.client.message.v1.ClientReceiveMessageEvents;
+import blade.addon.utils.debug.Debug;
 
 import java.util.regex.Matcher;
 import java.util.regex.Pattern;
@@ -16,20 +15,20 @@ public class CustomEvents {
 
     public static void init() {
         //party event
-        ClientReceiveMessageEvents.GAME.register((message, overlay) -> {
+        Events.ON_GAME_MESSAGE.register(message -> {
             String string = message.getString();
 
             Matcher matcher = PARTY_PATTERN.matcher(string);
-            if (!matcher.find()) return;
+            if (!matcher.find()) return false;
 
             int index = string.indexOf(":");
             if (index < PARTY_MSG_OFFSET) {
                 Debug.LOGGER.error("{} had bad index", string);
-                return;
+                return false;
             }
 
             String tempUsername = string.substring(PARTY_MSG_OFFSET, index).replaceAll("§.", "");
-            if (index + 2 >= string.length()) return;
+            if (index + 2 >= string.length()) return false;
             String sentMessage = string.substring(index + 2).strip();
 
             index = tempUsername.indexOf("]") + 2;
@@ -39,18 +38,22 @@ public class CustomEvents {
 
             String username = tempUsername;
             Events.ON_PARTY_MESSAGE.invoke(partyMessageEvent -> partyMessageEvent.sentMessage(username, sentMessage));
+
+            return false;
         });
 
         //leap event
-        ClientReceiveMessageEvents.GAME.register((message, overlay) -> {
+        Events.ON_GAME_MESSAGE.register(message -> {
             if (Location.inDungeon()) {
                 String string = message.getString();
 
                 Matcher matcher = LEAP_PATTERN.matcher(string);
-                if (!matcher.find()) return;
+                if (!matcher.find()) return false;
 
                 Events.ON_LEAP.invoke(leapEvent -> leapEvent.onLeap(matcher.group(1)));
             }
+
+            return false;
         });
     }
 }
- added sound on invincibility proc - fixed a crash when modifying some save folders - x can be used instead of * for multiplication - math results formatting can now be disabled - added a toggle to display distance on all classes - made DistanceToLedge be different for non mastermodeGabriel Ellnebrand · 2026-07-18 · a8de93c
查看变更
src/main/java/blade/addon/Bladeaddons.java                                             |  4 +++-
 src/main/java/blade/addon/features/dungeon/f7/invincibility/InvincibilityDuration.java |  4 +++-
 src/main/java/blade/addon/features/dungeon/f7/storm/DistanceToLedge.java               | 11 ++++++++++-
 src/main/java/blade/addon/features/other/SearchBar.java                                |  2 +-
 src/main/java/blade/addon/utils/MathParser.java                                        |  1 +
 src/main/java/blade/addon/utils/config/Config.java                                     |  2 ++
 src/main/java/blade/addon/utils/config/FolderUtility.java                              |  8 ++++++--
 src/main/java/blade/addon/utils/config/values/Dungeons.java                            |  5 +++++
 src/main/java/blade/addon/utils/config/values/ExtraOptions.java                        |  3 +++
 src/main/java/blade/addon/utils/config/values/Floor7.java                              |  3 +++
 src/main/java/blade/addon/utils/dungeon/Phase.java                                     | 13 +++++++++++--
 11 files changed, 48 insertions(+), 8 deletions(-)
diff --git a/src/main/java/blade/addon/Bladeaddons.java b/src/main/java/blade/addon/Bladeaddons.java
index 8cb6b94..435b298 100644
--- a/src/main/java/blade/addon/Bladeaddons.java
+++ b/src/main/java/blade/addon/Bladeaddons.java
@@ -51,7 +51,9 @@ public class Bladeaddons implements ModInitializer {
         FolderUtility.init();
         Components.init();
         Buttons.init();
-        Config.manager.load();
+        try {
+            Config.manager.load();
+        }catch (NullPointerException err) {}
         Keybinds.init();
         CustomEvents.init();
         Commands.init();
diff --git a/src/main/java/blade/addon/features/dungeon/f7/invincibility/InvincibilityDuration.java b/src/main/java/blade/addon/features/dungeon/f7/invincibility/InvincibilityDuration.java
index c5b792a..b136cdc 100644
--- a/src/main/java/blade/addon/features/dungeon/f7/invincibility/InvincibilityDuration.java
+++ b/src/main/java/blade/addon/features/dungeon/f7/invincibility/InvincibilityDuration.java
@@ -1,5 +1,6 @@
 package blade.addon.features.dungeon.f7.invincibility;
 
+import blade.addon.utils.Misc;
 import blade.addon.utils.config.values.Dungeons;
 import blade.addon.utils.events.Events;
 import blade.addon.utils.rendering.RenderUtils;
@@ -20,10 +21,11 @@ public class InvincibilityDuration {
 
     public static void proc() {
         ticks = MAX_DURATION;
+        Misc.sendSound(Dungeons.invincibilitySound);
     }
 
     public static boolean display() {
-        return  ticks > 0 && Dungeons.InvincibilityDuration;
+        return ticks > 0 && Dungeons.InvincibilityDuration;
     }
 
     public static void render(HUDComponent component, GuiGraphicsExtractor graphics) {
diff --git a/src/main/java/blade/addon/features/dungeon/f7/storm/DistanceToLedge.java b/src/main/java/blade/addon/features/dungeon/f7/storm/DistanceToLedge.java
index ff74deb..942b132 100644
--- a/src/main/java/blade/addon/features/dungeon/f7/storm/DistanceToLedge.java
+++ b/src/main/java/blade/addon/features/dungeon/f7/storm/DistanceToLedge.java
@@ -33,10 +33,19 @@ public class DistanceToLedge {
         return YELLOW_PAD.contains(player.position());
     }
 
+    public static boolean isPYClass() {
+        if (Floor7.displayDistanceOnAllClasses) return true;
+        if (Phase.isInMasterMode()) {
+            return DungeonClass.isClass(DungeonClass.MAGE) || DungeonClass.isClass(DungeonClass.ARCHER);
+        } else {
+            return DungeonClass.isClass(DungeonClass.TANK) || DungeonClass.isClass(DungeonClass.ARCHER);
+        }
+    }
+
     public static boolean display() {
         if (!Floor7.displayDistanceToLedge || !Location.inDungeon()) return false;
         if (Floor7.showDistanceAtYellowOnly && !isAtYellow()) return false;
-        return Phase.inP2() && !Phase.stormDead() && (DungeonClass.isClass(DungeonClass.MAGE) || DungeonClass.isClass(DungeonClass.ARCHER));
+        return Phase.inP2() && !Phase.stormDead() && isPYClass();
     }
 
     public static void render(HUDComponent component, GuiGraphicsExtractor graphics) {
diff --git a/src/main/java/blade/addon/features/other/SearchBar.java b/src/main/java/blade/addon/features/other/SearchBar.java
index a7c0b01..0322b3a 100644
--- a/src/main/java/blade/addon/features/other/SearchBar.java
+++ b/src/main/java/blade/addon/features/other/SearchBar.java
@@ -50,7 +50,7 @@ public class SearchBar {
         searchBar.extractRenderState(context, mouseX, mouseY, deltaTicks);
 
         if (!Double.isNaN(parsedValue)) {
-            String expression = "  §e= §2" + RenderUtils.formatNumber((float) parsedValue);
+            String expression = "  §e= §2" + (ExtraOptions.formatMathResult ? RenderUtils.formatNumber((float) parsedValue) : (long) parsedValue);
             Font textRenderer = Minecraft.getInstance().font;
             if (textRenderer == null) return;
 
diff --git a/src/main/java/blade/addon/utils/MathParser.java b/src/main/java/blade/addon/utils/MathParser.java
index 9683ad9..e1c48bc 100644
--- a/src/main/java/blade/addon/utils/MathParser.java
+++ b/src/main/java/blade/addon/utils/MathParser.java
@@ -291,6 +291,7 @@ public class MathParser {
                 int index = copiedString.indexOf(token);
                 if (index == 0) {
                     copiedString = copiedString.substring(token.length());
+                    if (token.equals("x")) token = "*";
                     output.add(token);
                     wasPrevNum = false;
                     continue;
diff --git a/src/main/java/blade/addon/utils/config/Config.java b/src/main/java/blade/addon/utils/config/Config.java
index c04ff33..55301d3 100644
--- a/src/main/java/blade/addon/utils/config/Config.java
+++ b/src/main/java/blade/addon/utils/config/Config.java
@@ -88,6 +88,7 @@ public class Config {
         invincibility.add(new ConfigBool(Component.literal("Invincibility duration"), () -> Dungeons.InvincibilityDuration, bool -> Dungeons.InvincibilityDuration = bool));
         invincibility.add(new ConfigBool(Component.literal("Duration changing color"), () -> Dungeons.useStatusColorForInvincibility, bool -> Dungeons.useStatusColorForInvincibility = bool));
         invincibility.add(new ConfigBool(Component.literal("Remove \"Mask\" in the text"), () -> Dungeons.removeMaskPart, bool -> Dungeons.removeMaskPart = bool));
+        invincibility.add(new ConfigSound(Component.literal("Proc sound"), Dungeons.invincibilitySound));
         dungeons.add(invincibility);
 
         ConfigSection classColors = new ConfigSection(Component.literal("Class Colors"));
@@ -183,6 +184,7 @@ public class Config {
         storm.add(new ConfigBool(Component.literal("First Death time"), () -> Floor7.enableStormDeathTime, bool -> Floor7.enableStormDeathTime = bool));
         storm.add(new ConfigBool(Component.literal("Distance to ledge"), () -> Floor7.displayDistanceToLedge, bool -> Floor7.displayDistanceToLedge = bool));
         storm.add(new ConfigBool(Component.literal("Only display distance at yellow"), () -> Floor7.showDistanceAtYellowOnly, bool -> Floor7.showDistanceAtYellowOnly = bool));
+        storm.add(new ConfigBool(Component.literal("Display distance on all classes"), () -> Floor7.displayDistanceOnAllClasses, bool -> Floor7.displayDistanceOnAllClasses = bool));
         storm.add(new ConfigBool(Component.literal("Warn if spirit mask is used"), () -> Floor7.notifyUsedSpiritMask, bool -> Floor7.notifyUsedSpiritMask = bool));
         storm.add(new ConfigBool(Component.literal("Pillar explode timer"), () -> Floor7.timePillarExplosion, bool -> Floor7.timePillarExplosion = bool));
         storm.add(new ConfigBool(Component.literal("Storm crushed notification"), () -> Floor7.notifyStormCrush, bool -> Floor7.notifyStormCrush = bool));
diff --git a/src/main/java/blade/addon/utils/config/FolderUtility.java b/src/main/java/blade/addon/utils/config/FolderUtility.java
index 9d3da0a..fe148bb 100644
--- a/src/main/java/blade/addon/utils/config/FolderUtility.java
+++ b/src/main/java/blade/addon/utils/config/FolderUtility.java
@@ -27,8 +27,12 @@ public class FolderUtility {
             }
         }
 
-        ProtectItem.itemManager.load();
-        PersonalBests.pbManager.load();
+        try {
+            ProtectItem.itemManager.load();
+            PersonalBests.pbManager.load();
+        } catch (NullPointerException err) {
+            Debug.LOGGER.error("Failed to load protected item data or personal best data");
+        }
     }
 
 }
diff --git a/src/main/java/blade/addon/utils/config/values/Dungeons.java b/src/main/java/blade/addon/utils/config/values/Dungeons.java
index cfea806..30015ce 100644
--- a/src/main/java/blade/addon/utils/config/values/Dungeons.java
+++ b/src/main/java/blade/addon/utils/config/values/Dungeons.java
@@ -1,7 +1,9 @@
 package blade.addon.utils.config.values;
 
 import blade.addon.features.dungeon.f7.invincibility.InvincibilityTimer;
+import config.practical.data.SoundData;
 import config.practical.manager.ConfigValue;
+import net.minecraft.sounds.SoundEvents;
 
 public class Dungeons {
 
@@ -163,4 +165,7 @@ public class Dungeons {
 
     @ConfigValue
     public static String customLeapMessage = "Leaped to <player>!";
+
+    @ConfigValue
+    public static SoundData invincibilitySound =  new SoundData(SoundEvents.NOTE_BLOCK_PLING.value(), 0, 1);
 }
diff --git a/src/main/java/blade/addon/utils/config/values/ExtraOptions.java b/src/main/java/blade/addon/utils/config/values/ExtraOptions.java
index 891f6a9..8b2449e 100644
--- a/src/main/java/blade/addon/utils/config/values/ExtraOptions.java
+++ b/src/main/java/blade/addon/utils/config/values/ExtraOptions.java
@@ -141,4 +141,7 @@ public class ExtraOptions {
 
     @ConfigValue
     public static int searchbarMissColor = 0xaa111111;
+
+    @ConfigValue
+    public static boolean formatMathResult = true;
 }
diff --git a/src/main/java/blade/addon/utils/config/values/Floor7.java b/src/main/java/blade/addon/utils/config/values/Floor7.java
index be80926..d9137dd 100644
--- a/src/main/java/blade/addon/utils/config/values/Floor7.java
+++ b/src/main/java/blade/addon/utils/config/values/Floor7.java
@@ -189,4 +189,7 @@ public class Floor7 {
 
     @ConfigValue
     public static int lbTickOffset = 10;
+
+    @ConfigValue
+    public static boolean displayDistanceOnAllClasses = false;
 }
diff --git a/src/main/java/blade/addon/utils/dungeon/Phase.java b/src/main/java/blade/addon/utils/dungeon/Phase.java
index 26ca223..fe0eb76 100644
--- a/src/main/java/blade/addon/utils/dungeon/Phase.java
+++ b/src/main/java/blade/addon/utils/dungeon/Phase.java
@@ -10,10 +10,12 @@ import blade.addon.utils.events.interfaces.PhaseEvent;
 import blade.addon.utils.events.interfaces.RunEndEvent;
 import config.practical.hud.HUDComponent;
 import config.practical.manager.ConfigValue;
+
 import java.util.ArrayList;
 import java.util.HashMap;
 import java.util.regex.Matcher;
 import java.util.regex.Pattern;
+
 import net.minecraft.client.Minecraft;
 import net.minecraft.client.gui.Font;
 import net.minecraft.network.chat.Component;
@@ -34,6 +36,7 @@ public class Phase {
     private static int currentPhase = -1;
     private static String floor = "";
 
+    private static boolean inMasterMode = false;
     private static boolean inFloor7 = false;
     private static boolean stormDead = false;
     private static boolean runOver = false;
@@ -85,6 +88,7 @@ public class Phase {
         }
         if (floor != null) {
             if (floor.contains("7")) inFloor7 = true;
+            if (floor.contains("M")) inMasterMode = true;
         }
 
         return false;
@@ -95,6 +99,7 @@ public class Phase {
         floor = null;
         currentPhase = -1;
         inFloor7 = false;
+        inMasterMode = false;
         stormDead = false;
         runOver = false;
     }
@@ -172,6 +177,10 @@ public class Phase {
         return inFloor7;
     }
 
+    public static boolean isInMasterMode() {
+        return inMasterMode;
+    }
+
     public static boolean runStarted() {
         return currentPhase >= 0;
     }
@@ -209,7 +218,7 @@ public class Phase {
     }
 
     public static boolean inP5() {
-        return currentPhase == 9 && inFloor7;
+        return currentPhase == 9 && inFloor7 && inMasterMode;
     }
 
     public static boolean runOver() {
@@ -218,7 +227,7 @@ public class Phase {
 
 
     public static double getPhaseTime(int index) {
-        if (index < 0 ||index >= currentSplits.size()) return 0;
+        if (index < 0 || index >= currentSplits.size()) return 0;
         return currentSplits.get(index).getRealTime();
     }
 
- made odin loadout keybinds compatible - added custom leap message - improved leap notification regionsGabriel Ellnebrand · 2026-07-18 · 0737a4b
查看变更
src/main/java/blade/addon/features/dungeon/LeapMessage.java               |  6 ++----
 src/main/java/blade/addon/features/dungeon/f7/terms/LeapNotification.java |  4 ++--
 src/main/java/blade/addon/features/other/SelectedPet.java                 | 40 ++++++++++++++++++++++++----------------
 src/main/java/blade/addon/mixin/MultiPlayerGameModeMixin.java             | 20 ++++++++++++++++++++
 src/main/java/blade/addon/utils/Commands.java                             |  3 +++
 src/main/java/blade/addon/utils/config/Config.java                        |  3 ++-
 src/main/java/blade/addon/utils/config/values/Dungeons.java               |  3 +++
 src/main/java/blade/addon/utils/dungeon/FillHelper.java                   |  1 +
 src/main/java/blade/addon/utils/events/CustomEvents.java                  |  4 ++--
 src/main/java/blade/addon/utils/events/interfaces/LeapEvent.java          |  4 +---
 src/main/resources/blade-addons.mixins.json                               | 27 ++++++++++++++-------------
 11 files changed, 74 insertions(+), 41 deletions(-)
diff --git a/src/main/java/blade/addon/features/dungeon/LeapMessage.java b/src/main/java/blade/addon/features/dungeon/LeapMessage.java
index aa87ba8..1f7f609 100644
--- a/src/main/java/blade/addon/features/dungeon/LeapMessage.java
+++ b/src/main/java/blade/addon/features/dungeon/LeapMessage.java
@@ -7,15 +7,13 @@ import net.minecraft.client.multiplayer.ClientPacketListener;
 
 public class LeapMessage {
 
-    private static final int INDEX_REMOVAL = 9;
-
     public static void init() {
-        Events.ON_LEAP.register(message -> {
+        Events.ON_LEAP.register(name -> {
             if (!Dungeons.enableLeapMessages) return false;
 
             ClientPacketListener networkHandler = Minecraft.getInstance().getConnection();
             if (networkHandler == null) return false;
-            networkHandler.sendCommand("pc " + message.getString().substring(INDEX_REMOVAL));
+            networkHandler.sendCommand("pc " + Dungeons.customLeapMessage.replace("<player>", name));
             return false;
         });
     }
diff --git a/src/main/java/blade/addon/features/dungeon/f7/terms/LeapNotification.java b/src/main/java/blade/addon/features/dungeon/f7/terms/LeapNotification.java
index 6c8e5b8..050c827 100644
--- a/src/main/java/blade/addon/features/dungeon/f7/terms/LeapNotification.java
+++ b/src/main/java/blade/addon/features/dungeon/f7/terms/LeapNotification.java
@@ -26,8 +26,8 @@ public class LeapNotification {
     private static final AABB[] REGIONS = {
             new AABB(91, 105, 46, 111, 127, 123),
             new AABB(17, 106, 121, 108, 145, 143),
-            new AABB(-2, 106, 51, 20, 145, 142),
-            new AABB(-1, 26, 29, 191, 145, 58),
+            new AABB(-2, 106, 51, 20, 145, 128),
+            new AABB( 20, 26, 29, 191, 145, 58),
             new AABB(3, 5, 0, 128, 48, 140)
     };
 
diff --git a/src/main/java/blade/addon/features/other/SelectedPet.java b/src/main/java/blade/addon/features/other/SelectedPet.java
index 2637809..a64fd6e 100644
--- a/src/main/java/blade/addon/features/other/SelectedPet.java
+++ b/src/main/java/blade/addon/features/other/SelectedPet.java
@@ -17,6 +17,9 @@ import net.minecraft.network.chat.Component;
 import net.minecraft.network.chat.MutableComponent;
 import net.minecraft.network.chat.Style;
 import net.minecraft.resources.Identifier;
+import net.minecraft.world.entity.player.Player;
+import net.minecraft.world.inventory.AbstractContainerMenu;
+import net.minecraft.world.inventory.ContainerInput;
 
 import java.util.List;
 import java.util.regex.Matcher;
@@ -77,6 +80,7 @@ public class SelectedPet {
 
                 summonPet(name, Component.literal(textName), getLevel(unformattedLine), true);
             }
+
             return false;
         });
 
@@ -113,28 +117,32 @@ public class SelectedPet {
             return false;
         });
 
-        Events.ON_SLOT_CLICKED.register((slot, slotId, button,clickType) -> {
-            if (!inLoadout || button != 0) return false;
+        ClientTickEvents.END_LEVEL_TICK.register((world -> {
+            tick = Math.max(tick - 1, 0);
+        }));
 
-            Component component = ItemUtil.findLore(slot.getItem(), "Pet: ");
-            if (component == null) return false;
+    }
 
-            String string = component.getString();
-            Matcher matcher = TAB_PET_PATTERN.matcher(string);
-            if (matcher.find()) {
-                String petName = matcher.group(1).replace(" ✦", "");
-                Style style = getStyle(component, petName);
-                summonPet(petName, Component.literal(petName).setStyle(style), getLevel(string), false);
-            }
+    public static void testLoadoutClick(int container, int slotId, int button, ContainerInput containerInput, Player player) {
+        if (!inLoadout || button != 0 || player == null) return;
 
+        AbstractContainerMenu containerMenu = player.containerMenu;
+        if (containerMenu == null) {
+            Misc.addChatMessage(Component.literal("container is null"));
+            return;
+        }
 
-            return false;
-        });
+        Component component = ItemUtil.findLore(containerMenu.getSlot(slotId).getItem(), "Pet: ");
+        if (component == null) return;
 
-        ClientTickEvents.END_LEVEL_TICK.register((world -> {
-            tick = Math.max(tick - 1, 0);
-        }));
 
+        String string = component.getString();
+        Matcher matcher = TAB_PET_PATTERN.matcher(string);
+        if (matcher.find()) {
+            String petName = matcher.group(1).replace(" ✦", "");
+            Style style = getStyle(component, petName);
+            summonPet(petName, Component.literal(petName).setStyle(style), getLevel(string), false);
+        }
     }
 
     private static void despawnPet() {
diff --git a/src/main/java/blade/addon/mixin/MultiPlayerGameModeMixin.java b/src/main/java/blade/addon/mixin/MultiPlayerGameModeMixin.java
new file mode 100644
index 0000000..4a2d0c2
--- /dev/null
+++ b/src/main/java/blade/addon/mixin/MultiPlayerGameModeMixin.java
@@ -0,0 +1,20 @@
+package blade.addon.mixin;
+
+import blade.addon.features.other.SelectedPet;
+import net.minecraft.client.multiplayer.MultiPlayerGameMode;
+import net.minecraft.world.entity.player.Player;
+import net.minecraft.world.inventory.ContainerInput;
+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;
+
+@Mixin(MultiPlayerGameMode.class)
+public class MultiPlayerGameModeMixin {
+
+    @Inject(method = "handleContainerInput", at=@At("HEAD"))
+    public void handleInventoryMouseClick(int container, int slotId, int button, ContainerInput containerInput, Player player, CallbackInfo ci) {
+        SelectedPet.testLoadoutClick(container, slotId, button, containerInput, player);
+    }
+
+}
diff --git a/src/main/java/blade/addon/utils/Commands.java b/src/main/java/blade/addon/utils/Commands.java
index b8c5515..f0f73c4 100644
--- a/src/main/java/blade/addon/utils/Commands.java
+++ b/src/main/java/blade/addon/utils/Commands.java
@@ -44,6 +44,9 @@ public class Commands {
 
                             .then(ClientCommands.literal("ij").executes(context -> FillHelper.fillItem(FillHelper.INFLATABLE_JERRY, 64, 64, false)))
 
+                            .then(ClientCommands.literal("sp").executes(context -> FillHelper.fillItem(FillHelper.SPRIT_LEAP, 16, 16, false)))
+
+
                             .then(ClientCommands.literal("refill")
                                     .executes(
                                             context -> {
diff --git a/src/main/java/blade/addon/utils/config/Config.java b/src/main/java/blade/addon/utils/config/Config.java
index c25f855..c04ff33 100644
--- a/src/main/java/blade/addon/utils/config/Config.java
+++ b/src/main/java/blade/addon/utils/config/Config.java
@@ -77,7 +77,7 @@ public class Config {
 
         ConfigSection deathMessage = new ConfigSection(Component.literal("Death message"));
         deathMessage.add(new ConfigBool(Component.literal("Send death message"), () -> Dungeons.sendDeathMessage, bool -> Dungeons.sendDeathMessage = bool));
-        deathMessage.add(new ConfigString(Component.literal("Message sent (<player> replaces with the players name) "), () -> Dungeons.deathMessage, str -> Dungeons.deathMessage = str));
+        deathMessage.add(new ConfigString(Component.literal("Message sent, (<player> gets replaced with the players name) "), () -> Dungeons.deathMessage, str -> Dungeons.deathMessage = str));
         //dungeons.add(deathMessage);
 
         ConfigSection invincibility = new ConfigSection(Component.literal("Invincibility Timer"));
@@ -121,6 +121,7 @@ public class Config {
 
         dungeons.add(chests);
         dungeons.add(new ConfigBool(Component.literal("Leap message"), () -> Dungeons.enableLeapMessages, bool -> Dungeons.enableLeapMessages = bool));
+        dungeons.add(new ConfigString(Component.literal("Custom leap message, (<player> gets replaced with the players name)"), () -> Dungeons.customLeapMessage, str -> Dungeons.customLeapMessage = str));
         dungeons.add(new ConfigBool(Component.literal("Auto requeue"), () -> Dungeons.enableAutoRequeue, bool -> Dungeons.enableAutoRequeue = bool));
 
         ConfigSection expo = new ConfigSection(Component.literal("Explosive shot"));
diff --git a/src/main/java/blade/addon/utils/config/values/Dungeons.java b/src/main/java/blade/addon/utils/config/values/Dungeons.java
index 29073a8..cfea806 100644
--- a/src/main/java/blade/addon/utils/config/values/Dungeons.java
+++ b/src/main/java/blade/addon/utils/config/values/Dungeons.java
@@ -160,4 +160,7 @@ public class Dungeons {
 
     @ConfigValue
     public static boolean dontHighlightVisibleTeammates = false;
+
+    @ConfigValue
+    public static String customLeapMessage = "Leaped to <player>!";
 }
diff --git a/src/main/java/blade/addon/utils/dungeon/FillHelper.java b/src/main/java/blade/addon/utils/dungeon/FillHelper.java
index 4cf4ed5..80e19af 100644
--- a/src/main/java/blade/addon/utils/dungeon/FillHelper.java
+++ b/src/main/java/blade/addon/utils/dungeon/FillHelper.java
@@ -12,6 +12,7 @@ public class FillHelper {
     public static final String SUPERBOOM_TNT = "Superboom TNT";
     public static final String INFLATABLE_JERRY = "Inflatable Jerry";
     public static final String DECOY = "Decoy";
+    public static final String SPRIT_LEAP = "Spirit Leap";
 
     /**
      * @param itemName Name of item
diff --git a/src/main/java/blade/addon/utils/events/CustomEvents.java b/src/main/java/blade/addon/utils/events/CustomEvents.java
index db805e8..7120ce9 100644
--- a/src/main/java/blade/addon/utils/events/CustomEvents.java
+++ b/src/main/java/blade/addon/utils/events/CustomEvents.java
@@ -10,7 +10,7 @@ import java.util.regex.Pattern;
 public class CustomEvents {
 
     private static final Pattern PARTY_PATTERN = Pattern.compile("^§9Party §8>");
-    private static final Pattern LEAP_PATTERN = Pattern.compile("^You have teleported to .*!$");
+    private static final Pattern LEAP_PATTERN = Pattern.compile("^You have teleported to (.*)!$");
 
     private static final int PARTY_MSG_OFFSET = 11;
 
@@ -49,7 +49,7 @@ public class CustomEvents {
                 Matcher matcher = LEAP_PATTERN.matcher(string);
                 if (!matcher.find()) return;
 
-                Events.ON_LEAP.invoke(leapEvent -> leapEvent.onLeap(message));
+                Events.ON_LEAP.invoke(leapEvent -> leapEvent.onLeap(matcher.group(1)));
             }
         });
     }
diff --git a/src/main/java/blade/addon/utils/events/interfaces/LeapEvent.java b/src/main/java/blade/addon/utils/events/interfaces/LeapEvent.java
index 33b2f64..28c80ec 100644
--- a/src/main/java/blade/addon/utils/events/interfaces/LeapEvent.java
+++ b/src/main/java/blade/addon/utils/events/interfaces/LeapEvent.java
@@ -1,7 +1,5 @@
 package blade.addon.utils.events.interfaces;
 
-import net.minecraft.network.chat.Component;
-
 public interface LeapEvent {
-    boolean onLeap(Component message);
+    boolean onLeap(String name);
 }
diff --git a/src/main/resources/blade-addons.mixins.json b/src/main/resources/blade-addons.mixins.json
index 8a15d2a..4fad7f5 100644
--- a/src/main/resources/blade-addons.mixins.json
+++ b/src/main/resources/blade-addons.mixins.json
@@ -3,16 +3,16 @@
   "package": "blade.addon.mixin",
   "compatibilityLevel": "JAVA_21",
   "mixins": [
-    "LevelChunkMixin",
-    "ConnectionMixin",
+    "ArrowLayerMixin",
     "ClientPacketListenerMixin",
+    "ConnectionMixin",
     "EnderPearlItemMixin",
     "EntityMixin",
+    "InventoryMixin",
     "ItemStackMixin",
+    "LevelChunkMixin",
     "LivingEntityMixin",
-    "InventoryMixin",
-    "ShovelItemMixin",
-    "ArrowLayerMixin"
+    "ShovelItemMixin"
   ],
   "injectors": {
     "defaultRequire": 1
@@ -21,24 +21,25 @@
     "requireAnnotations": true
   },
   "client": [
+    "AbstractContainerScreenMixin",
     "BossHealthOverlayMixin",
     "ChatComponentMixin",
     "ChatScreenMixin",
-    "LocalPlayerMixin",
     "ClientPacketListenerMixin",
-    "GuiGraphicsExtractorMixin",
-    "EntityRendererMixin",
+    "EffectsInInventoryMixin",
     "EntityRenderDispatcherMixin",
-    "AbstractContainerScreenMixin",
-    "HeldItemRendererMixin",
+    "EntityRendererMixin",
+    "GuiGraphicsExtractorMixin",
     "GuiMixin",
+    "HeldItemRendererMixin",
     "InventoryScreenMixin",
     "KeyboardHandlerMixin",
+    "LocalPlayerMixin",
     "MinecraftMixin",
     "MouseHandlerMixin",
-    "RecipeBookScreenMixin",
+    "MultiPlayerGameModeMixin",
     "RecipeBookComponentMixin",
-    "SkullBlockRendererMixin",
-    "EffectsInInventoryMixin"
+    "RecipeBookScreenMixin",
+    "SkullBlockRendererMixin"
   ]
 }
\ No newline at end of file
- added death messages - made none uuid items protectable - added hide visible teammate's highlightGabriel Ellnebrand · 2026-07-15 · 5bbcbc2
查看变更
src/main/java/blade/addon/Bladeaddons.java                          |  2 +-
 src/main/java/blade/addon/features/dungeon/DeathMessage.java        | 29 +++++++++++++----------------
 src/main/java/blade/addon/features/highlight/TeammateHighlight.java |  1 +
 src/main/java/blade/addon/features/item/ProtectItem.java            | 29 +++++++++++++++++++----------
 src/main/java/blade/addon/utils/config/Config.java                  |  3 ++-
 src/main/java/blade/addon/utils/config/values/Dungeons.java         |  3 +++
 src/main/java/blade/addon/utils/dungeon/DungeonClass.java           | 11 +++++++++++
 7 files changed, 50 insertions(+), 28 deletions(-)
diff --git a/src/main/java/blade/addon/Bladeaddons.java b/src/main/java/blade/addon/Bladeaddons.java
index 9d41a13..8cb6b94 100644
--- a/src/main/java/blade/addon/Bladeaddons.java
+++ b/src/main/java/blade/addon/Bladeaddons.java
@@ -129,7 +129,7 @@ public class Bladeaddons implements ModInitializer {
         DragonTracer.init();
         ReaperDisplay.init();
         StormLBTimer.init();
-        //DeathMessage.init(); TODO finish this
+        DeathMessage.init();
         BlockPetMenu.init();
     }
 }
\ No newline at end of file
diff --git a/src/main/java/blade/addon/features/dungeon/DeathMessage.java b/src/main/java/blade/addon/features/dungeon/DeathMessage.java
index e70f34e..fab6b79 100644
--- a/src/main/java/blade/addon/features/dungeon/DeathMessage.java
+++ b/src/main/java/blade/addon/features/dungeon/DeathMessage.java
@@ -2,6 +2,7 @@ package blade.addon.features.dungeon;
 
 import blade.addon.utils.Location;
 import blade.addon.utils.config.values.Dungeons;
+import blade.addon.utils.dungeon.DungeonClass;
 import blade.addon.utils.events.Events;
 import net.minecraft.client.Minecraft;
 import net.minecraft.client.multiplayer.ClientPacketListener;
@@ -11,11 +12,7 @@ import java.util.regex.Pattern;
 
 public class DeathMessage {
 
-    private static final Pattern[] patterns = new Pattern[]{
-        Pattern.compile("^ ☠ (.*) was killed by *."),
-        Pattern.compile("^ ☠ (.*) died to a *."),
-        Pattern.compile("^ ☠ (.*) fell to their death *."),
-    };
+    private static final Pattern PATTERN = Pattern.compile("^ ☠ (.+) and became a ghost\\.$");
 
     public static void init() {
 
@@ -24,17 +21,17 @@ public class DeathMessage {
             if (!Location.inDungeon() || !Dungeons.sendDeathMessage) return false;
 
             String msg = text.getString();
-            for(Pattern pattern: patterns) {
-                Matcher matcher = pattern.matcher(msg);
-                if (matcher.find()) {
-                    String name = matcher.group(1);
-
-                    ClientPacketListener networkHandler = Minecraft.getInstance().getConnection();
-                    if (networkHandler == null) return false;
-                    networkHandler.sendCommand("pc " + createMessage(name));
-                    return false;
-                }
-            }
+
+            Matcher matcher = PATTERN.matcher(msg);
+            if (!matcher.find()) return false;
+
+            String message = matcher.group(1);
+            String name = DungeonClass.getNameFromMessage(message);
+            if (name == null) return false;
+
+            ClientPacketListener networkHandler = Minecraft.getInstance().getConnection();
+            if (networkHandler == null) return false;
+            networkHandler.sendCommand("pc " + createMessage(name));
             return false;
         });
 
diff --git a/src/main/java/blade/addon/features/highlight/TeammateHighlight.java b/src/main/java/blade/addon/features/highlight/TeammateHighlight.java
index cdf3563..83162a0 100644
--- a/src/main/java/blade/addon/features/highlight/TeammateHighlight.java
+++ b/src/main/java/blade/addon/features/highlight/TeammateHighlight.java
@@ -51,6 +51,7 @@ public class TeammateHighlight {
 
         teammates.forEach(player -> {
             if (Dungeons.dontHighlightHiddenTeammates && HidePlayers.shouldHidePlayers(player)) return;
+            if (Dungeons.dontHighlightVisibleTeammates && !HidePlayers.shouldHidePlayers(player)) return;
             DungeonClass clazz = DungeonClass.getClass(player);
             if (clazz == null) return;
 
diff --git a/src/main/java/blade/addon/features/item/ProtectItem.java b/src/main/java/blade/addon/features/item/ProtectItem.java
index d46f8fb..b891e90 100644
--- a/src/main/java/blade/addon/features/item/ProtectItem.java
+++ b/src/main/java/blade/addon/features/item/ProtectItem.java
@@ -51,18 +51,24 @@ public class ProtectItem {
         }
 
         ItemStack item = player.getInventory().getSelectedItem();
-        String uuid = ItemUtil.getUuid(item);
-        if (uuid == null) {
-            Misc.addChatMessage(Component.literal("Cant protect this item, it does not have a uuid"));
+        String saveString = ItemUtil.getUuid(item);
+
+        if (saveString == null) {
+            saveString = ItemUtil.getId(item);
+        }
+
+        if (saveString == null) {
+            Misc.addChatMessage(Component.literal("Cant protect this item, the item does not have a uuid or id"));
             return;
         }
+
         ProtectedItemHolder holder = (ProtectedItemHolder) (Object) item;
-        if (protectedItems.contains(uuid)) {
-            protectedItems.remove(uuid);
+        if (protectedItems.contains(saveString)) {
+            protectedItems.remove(saveString);
             holder.blade_addons$setProtected(false);
             Misc.addChatMessage(Component.literal("Item ").append(item.getHoverName()).append(" is NOT protected anymore"));
         } else {
-            protectedItems.add(uuid);
+            protectedItems.add(saveString);
             holder.blade_addons$setProtected(true);
             Misc.addChatMessage(Component.literal("Item ").append(item.getHoverName()).append(" is protected"));
         }
@@ -72,14 +78,17 @@ public class ProtectItem {
 
     public static boolean protect(ItemStack stack) {
         if (stopProtectItem) return false;
-        String uuid = ItemUtil.getUuid(stack);
+        String saveString = ItemUtil.getUuid(stack);
+
+        if (saveString == null) {
+            saveString = ItemUtil.getId(stack);
+        }
 
-        //TODO: add smth for non uuid items
-        if (uuid == null) {
+        if (saveString == null) {
             return false;
         }
 
-        return protectedItems.contains(uuid);
+        return protectedItems.contains(saveString);
     }
 
     private static void draw(GuiGraphicsExtractor context, ItemStack stack, int x, int y) {
diff --git a/src/main/java/blade/addon/utils/config/Config.java b/src/main/java/blade/addon/utils/config/Config.java
index c2704b8..c25f855 100644
--- a/src/main/java/blade/addon/utils/config/Config.java
+++ b/src/main/java/blade/addon/utils/config/Config.java
@@ -107,7 +107,8 @@ public class Config {
         hidePlayers.add(new ConfigBool(Component.literal("SS only hides before terms"), () -> Dungeons.hideBeforeTermsOnly, bool -> Dungeons.hideBeforeTermsOnly = bool));
         hidePlayers.add(new ConfigBool(Component.literal("Hide players in range"), () -> Dungeons.hidePlayersInRange, bool -> Dungeons.hidePlayersInRange = bool));
         hidePlayers.add(new ConfigDouble(Component.literal("Hiding range"), () -> Dungeons.hidePlayerRange, num -> Dungeons.hidePlayerRange = num, 1, 0, 7));
-        hidePlayers.add(new ConfigBool(Component.literal("Hide teammate highlight"), () -> Dungeons.dontHighlightHiddenTeammates, bool -> Dungeons.dontHighlightHiddenTeammates = bool));
+        hidePlayers.add(new ConfigBool(Component.literal("Hide hidden teammate's highlight"), () -> Dungeons.dontHighlightHiddenTeammates, bool -> Dungeons.dontHighlightHiddenTeammates = bool));
+        hidePlayers.add(new ConfigBool(Component.literal("Hide visible teammate's highlight"), () -> Dungeons.dontHighlightVisibleTeammates, bool -> Dungeons.dontHighlightVisibleTeammates = bool));
 
 
         dungeons.add(hidePlayers);
diff --git a/src/main/java/blade/addon/utils/config/values/Dungeons.java b/src/main/java/blade/addon/utils/config/values/Dungeons.java
index 3ea443a..29073a8 100644
--- a/src/main/java/blade/addon/utils/config/values/Dungeons.java
+++ b/src/main/java/blade/addon/utils/config/values/Dungeons.java
@@ -157,4 +157,7 @@ public class Dungeons {
 
     @ConfigValue
     public static boolean blockRemovingPet = false;
+
+    @ConfigValue
+    public static boolean dontHighlightVisibleTeammates = false;
 }
diff --git a/src/main/java/blade/addon/utils/dungeon/DungeonClass.java b/src/main/java/blade/addon/utils/dungeon/DungeonClass.java
index b43fe10..6f321ac 100644
--- a/src/main/java/blade/addon/utils/dungeon/DungeonClass.java
+++ b/src/main/java/blade/addon/utils/dungeon/DungeonClass.java
@@ -9,6 +9,8 @@ import blade.addon.utils.events.Events;
 import net.fabricmc.fabric.api.client.message.v1.ClientReceiveMessageEvents;
 import net.minecraft.network.chat.Component;
 import net.minecraft.world.entity.player.Player;
+
+import java.util.Iterator;
 import java.util.concurrent.ConcurrentHashMap;
 import java.util.regex.Matcher;
 import java.util.regex.Pattern;
@@ -98,6 +100,15 @@ public enum DungeonClass {
         return nameClassMap.containsKey(name);
     }
 
+    public static String getNameFromMessage(String message) {
+        for (Iterator<String> it = nameClassMap.keys().asIterator(); it.hasNext(); ) {
+            String name = it.next();
+            if (message.contains(name)) return name;
+        }
+
+        return null;
+    }
+
     public static int getColor(DungeonClass dungeonClass) {
         if (dungeonClass == null) return 0xffffffff;
 
- added storm lb timerGabriel Ellnebrand · 2026-07-15 · 2d33430
查看变更
src/main/java/blade/addon/Bladeaddons.java                        |  4 +++-
 src/main/java/blade/addon/features/dungeon/f7/StormLBTimer.java   | 54 ++++++++++++++++++++++++++++++++++++++++++++++++++++++
 src/main/java/blade/addon/utils/config/Config.java                |  5 +++++
 src/main/java/blade/addon/utils/config/components/Components.java | 39 +++++++++++++--------------------------
 src/main/java/blade/addon/utils/config/values/Floor7.java         |  6 ++++++
 5 files changed, 81 insertions(+), 27 deletions(-)
diff --git a/src/main/java/blade/addon/Bladeaddons.java b/src/main/java/blade/addon/Bladeaddons.java
index 38a4c4a..9d41a13 100644
--- a/src/main/java/blade/addon/Bladeaddons.java
+++ b/src/main/java/blade/addon/Bladeaddons.java
@@ -4,6 +4,7 @@ import blade.addon.features.dungeon.*;
 import blade.addon.features.dungeon.f7.BossWaypoints;
 import blade.addon.features.dungeon.f7.PredevTimer;
 import blade.addon.features.dungeon.f7.RelicTimer;
+import blade.addon.features.dungeon.f7.StormLBTimer;
 import blade.addon.features.dungeon.f7.dragons.DragSpawnTimer;
 import blade.addon.features.dungeon.f7.dragons.DragonHealth;
 import blade.addon.features.dungeon.f7.dragons.DragonTracer;
@@ -127,7 +128,8 @@ public class Bladeaddons implements ModInitializer {
         CurrentSection.init();
         DragonTracer.init();
         ReaperDisplay.init();
-        BlockPetMenu.init();
+        StormLBTimer.init();
         //DeathMessage.init(); TODO finish this
+        BlockPetMenu.init();
     }
 }
\ No newline at end of file
diff --git a/src/main/java/blade/addon/features/dungeon/f7/StormLBTimer.java b/src/main/java/blade/addon/features/dungeon/f7/StormLBTimer.java
new file mode 100644
index 0000000..c42c3b8
--- /dev/null
+++ b/src/main/java/blade/addon/features/dungeon/f7/StormLBTimer.java
@@ -0,0 +1,54 @@
+package blade.addon.features.dungeon.f7;
+
+import blade.addon.utils.Constants;
+import blade.addon.utils.Location;
+import blade.addon.utils.config.values.Floor7;
+import blade.addon.utils.dungeon.DungeonClass;
+import blade.addon.utils.dungeon.Phase;
+import blade.addon.utils.events.Events;
+import blade.addon.utils.rendering.RenderUtils;
+import config.practical.hud.HUDComponent;
+import net.minecraft.client.gui.GuiGraphicsExtractor;
+
+public class StormLBTimer {
+
+    private static final int BASE_LB_TICK = 20 * 34;
+    private static final int DISPLAY_DURATION_TICK = 20 * 5;
+
+    private static int tick = 0;
+
+    public static void init() {
+        Events.ON_SERVER_TICK.register(() -> {
+            if (Location.inDungeon() && Phase.inP2() && !Phase.stormDead()) tick++;
+            return false;
+        });
+
+        Events.ON_LOCATION_CHANGE.register(newLocation -> {
+            if (Location.inDungeon()) {
+                tick = 0;
+            }
+            return false;
+        });
+    }
+
+    private static int getCurrentTickTime() {
+        return BASE_LB_TICK + Floor7.lbTickOffset - tick;
+    }
+
+    private static boolean validTime() {
+        int deltaTick = getCurrentTickTime();
+        return deltaTick > 0 && deltaTick <= DISPLAY_DURATION_TICK;
+
+    }
+
+    public static boolean display() {
+        return Floor7.showLBTimer && Location.inDungeon() && Phase.inP2() && !Phase.stormDead() && validTime() && DungeonClass.isClass(DungeonClass.ARCHER);
+    }
+
+    public static void render(HUDComponent component, GuiGraphicsExtractor graphics) {
+        int deltaTick = getCurrentTickTime();
+        double num = deltaTick * Constants.TICK_DURATION;
+        RenderUtils.drawTimer(component, graphics, num, RenderUtils.getStatusColor(60, 30, deltaTick));
+    }
+
+}
diff --git a/src/main/java/blade/addon/utils/config/Config.java b/src/main/java/blade/addon/utils/config/Config.java
index 400d190..c2704b8 100644
--- a/src/main/java/blade/addon/utils/config/Config.java
+++ b/src/main/java/blade/addon/utils/config/Config.java
@@ -245,6 +245,11 @@ public class Config {
         dragon.add(new ConfigBool(Component.literal("Render dragon spawn tracer"), () -> Floor7.dragonTracer, bool -> Floor7.dragonTracer = bool));
         floor7.add(dragon);
 
+        ConfigSection stormlb = new ConfigSection(Component.literal("Storm lb"));
+        stormlb.add(new ConfigBool(Component.literal("Enable"), () -> Floor7.showLBTimer, bool ->  Floor7.showLBTimer = bool));
+        stormlb.add(new ConfigInt(Component.literal("Tick offset (34:00 is at 0)"), () -> Floor7.lbTickOffset, num -> Floor7.lbTickOffset = num, 1, 0, 20));
+        floor7.add(stormlb);
+
         return floor7;
     }
 
diff --git a/src/main/java/blade/addon/utils/config/components/Components.java b/src/main/java/blade/addon/utils/config/components/Components.java
index 7dea28b..93af757 100644
--- a/src/main/java/blade/addon/utils/config/components/Components.java
+++ b/src/main/java/blade/addon/utils/config/components/Components.java
@@ -1,38 +1,22 @@
 package blade.addon.utils.config.components;
 
-import blade.addon.features.dungeon.BloodNotifier;
-import blade.addon.features.dungeon.ChestCounter;
-import blade.addon.features.dungeon.QuizTimer;
-import blade.addon.features.dungeon.RunStartValidator;
-import blade.addon.features.dungeon.KeyNotifier;
-import blade.addon.features.dungeon.SecretSpawnTimer;
-import blade.addon.features.dungeon.WarpCooldown;
-import blade.addon.features.dungeon.f7.maxor.CrystalSpawn;
-import blade.addon.features.dungeon.f7.maxor.MaxorTickTimer;
-import blade.addon.features.dungeon.f7.storm.DistanceToLedge;
-import blade.addon.features.dungeon.f7.dragons.DragSpawnTimer;
-import blade.addon.features.dungeon.f7.storm.PillarExplode;
+import blade.addon.features.dungeon.*;
 import blade.addon.features.dungeon.f7.RelicTimer;
-import blade.addon.features.dungeon.f7.storm.StormTickTimer;
+import blade.addon.features.dungeon.f7.StormLBTimer;
+import blade.addon.features.dungeon.f7.dragons.DragSpawnTimer;
 import blade.addon.features.dungeon.f7.invincibility.InvincibilityDuration;
 import blade.addon.features.dungeon.f7.invincibility.InvincibilityTimer;
 import blade.addon.features.dungeon.f7.location.LocationNotifier;
+import blade.addon.features.dungeon.f7.maxor.CrystalSpawn;
 import blade.addon.features.dungeon.f7.maxor.MaxorStun;
-import blade.addon.features.dungeon.f7.terms.CurrentSection;
-import blade.addon.features.dungeon.f7.terms.GoldorTickTimer;
-import blade.addon.features.dungeon.f7.terms.LeapNotification;
-import blade.addon.features.dungeon.f7.terms.MelodyWarning;
-import blade.addon.features.dungeon.f7.terms.DeviceNotifier;
-import blade.addon.features.dungeon.f7.terms.SectionCompletion;
-import blade.addon.features.dungeon.f7.terms.SectionProgress;
-import blade.addon.features.dungeon.f7.terms.TermStartTimer;
+import blade.addon.features.dungeon.f7.maxor.MaxorTickTimer;
+import blade.addon.features.dungeon.f7.storm.DistanceToLedge;
+import blade.addon.features.dungeon.f7.storm.PillarExplode;
+import blade.addon.features.dungeon.f7.storm.StormTickTimer;
+import blade.addon.features.dungeon.f7.terms.*;
 import blade.addon.features.item.HeldItemToolTip;
-import blade.addon.features.other.ArrowSwapper;
-import blade.addon.features.other.KickedTimer;
-import blade.addon.features.other.RagDisplay;
-import blade.addon.features.other.ReaperDisplay;
-import blade.addon.features.other.SelectedPet;
 import blade.addon.features.notifications.Notifications;
+import blade.addon.features.other.*;
 import blade.addon.utils.config.values.Dungeons;
 import blade.addon.utils.config.values.ExtraOptions;
 import blade.addon.utils.config.values.Floor7;
@@ -169,4 +153,7 @@ public class Components {
 
     @ConfigValue
     public static HUDComponent reaperDisplay = new HUDComponent(0, 0, TICK_TIMER_WIDTH, 10, 1, "reaper display", ReaperDisplay::display, ReaperDisplay::render, () -> ExtraOptions.enableReaperDisplay);
+
+    @ConfigValue
+    public static HUDComponent stormLbTimer = new HUDComponent(0, 0, TICK_TIMER_WIDTH, 10, 1, "Storm lb timer", StormLBTimer::display, StormLBTimer::render, () -> Floor7.showLBTimer);
 }
diff --git a/src/main/java/blade/addon/utils/config/values/Floor7.java b/src/main/java/blade/addon/utils/config/values/Floor7.java
index db57bcc..be80926 100644
--- a/src/main/java/blade/addon/utils/config/values/Floor7.java
+++ b/src/main/java/blade/addon/utils/config/values/Floor7.java
@@ -183,4 +183,10 @@ public class Floor7 {
 
     @ConfigValue
     public static boolean assumeSplitEE2 = false;
+
+    @ConfigValue
+    public static boolean showLBTimer = false;
+
+    @ConfigValue
+    public static int lbTickOffset = 10;
 }
- added block "convert pet to item" while in a runGabriel Ellnebrand · 2026-07-15 · c9a6e6d
查看变更
src/main/java/blade/addon/Bladeaddons.java                            |  1 +
 src/main/java/blade/addon/features/other/BlockPetMenu.java            | 30 ++++++++++++++++++++++++++++++
 src/main/java/blade/addon/features/other/SelectedPet.java             |  2 +-
 src/main/java/blade/addon/mixin/AbstractContainerScreenMixin.java     | 19 +++++++++++--------
 src/main/java/blade/addon/utils/config/Config.java                    |  1 +
 src/main/java/blade/addon/utils/config/values/Dungeons.java           |  3 +++
 src/main/java/blade/addon/utils/events/interfaces/SlotClickEvent.java |  2 +-
 7 files changed, 48 insertions(+), 10 deletions(-)
diff --git a/src/main/java/blade/addon/Bladeaddons.java b/src/main/java/blade/addon/Bladeaddons.java
index ed94832..38a4c4a 100644
--- a/src/main/java/blade/addon/Bladeaddons.java
+++ b/src/main/java/blade/addon/Bladeaddons.java
@@ -127,6 +127,7 @@ public class Bladeaddons implements ModInitializer {
         CurrentSection.init();
         DragonTracer.init();
         ReaperDisplay.init();
+        BlockPetMenu.init();
         //DeathMessage.init(); TODO finish this
     }
 }
\ No newline at end of file
diff --git a/src/main/java/blade/addon/features/other/BlockPetMenu.java b/src/main/java/blade/addon/features/other/BlockPetMenu.java
new file mode 100644
index 0000000..a6e02de
--- /dev/null
+++ b/src/main/java/blade/addon/features/other/BlockPetMenu.java
@@ -0,0 +1,30 @@
+package blade.addon.features.other;
+
+import blade.addon.utils.Location;
+import blade.addon.utils.config.values.Dungeons;
+import blade.addon.utils.events.Events;
+
+public class BlockPetMenu {
+
+    private static boolean inPetMenu = false;
+
+    public static void init() {
+        Events.ON_SCREEN.register(screen -> {
+            if (screen == null) {
+                inPetMenu = false;
+                return false;
+            }
+
+            inPetMenu = screen.getTitle().getString().contains("Pets");
+            return false;
+        });
+
+        Events.ON_SLOT_CLICKED.register((slot,slotId, button, containerInput) -> {
+            if (inPetMenu && button == 1 && Dungeons.blockRemovingPet && Location.inDungeon()) {
+                return slotId >= 9 && slotId <= 44;
+            }
+            return false;
+        });
+    }
+
+}
diff --git a/src/main/java/blade/addon/features/other/SelectedPet.java b/src/main/java/blade/addon/features/other/SelectedPet.java
index 0276e54..2637809 100644
--- a/src/main/java/blade/addon/features/other/SelectedPet.java
+++ b/src/main/java/blade/addon/features/other/SelectedPet.java
@@ -113,7 +113,7 @@ public class SelectedPet {
             return false;
         });
 
-        Events.ON_SLOT_CLICKED.register((slot, button,clickType) -> {
+        Events.ON_SLOT_CLICKED.register((slot, slotId, button,clickType) -> {
             if (!inLoadout || button != 0) return false;
 
             Component component = ItemUtil.findLore(slot.getItem(), "Pet: ");
diff --git a/src/main/java/blade/addon/mixin/AbstractContainerScreenMixin.java b/src/main/java/blade/addon/mixin/AbstractContainerScreenMixin.java
index c128137..f22612d 100644
--- a/src/main/java/blade/addon/mixin/AbstractContainerScreenMixin.java
+++ b/src/main/java/blade/addon/mixin/AbstractContainerScreenMixin.java
@@ -36,7 +36,7 @@ public abstract class AbstractContainerScreenMixin<T extends AbstractContainerMe
         super(title);
     }
 
-    @Inject(method = "extractRenderState", at=@At("TAIL"))
+    @Inject(method = "extractRenderState", at = @At("TAIL"))
     private static void render(GuiGraphicsExtractor graphics, int mouseX, int mouseY, float deltaTicks, CallbackInfo ci) {
         SearchBar.render(graphics, mouseX, mouseY, deltaTicks);
     }
@@ -58,17 +58,18 @@ public abstract class AbstractContainerScreenMixin<T extends AbstractContainerMe
         DrawEvents.INVENTORY_SLOT_AFTER.invoke(event -> event.draw(graphics, stack, x, y));
     }
 
-    @Inject(method = "keyPressed", at=@At("HEAD"), cancellable = true)
+    @Inject(method = "keyPressed", at = @At("HEAD"), cancellable = true)
     private void keyPressed(KeyEvent input, CallbackInfoReturnable<Boolean> cir) {
         if (SearchBar.keyPressed(input)) cir.setReturnValue(false);
     }
 
-    @Inject(method = "mouseClicked", at=@At("HEAD"))
+    @Inject(method = "mouseClicked", at = @At("HEAD"))
     private void onMouseClick(MouseButtonEvent click, boolean doubled, CallbackInfoReturnable<Boolean> cir) {
         SearchBar.onMouseClick(click);
     }
-    @Inject(method ="slotClicked", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/gui/screens/inventory/AbstractContainerScreen;onMouseClickAction(Lnet/minecraft/world/inventory/Slot;Lnet/minecraft/world/inventory/ContainerInput;)V"), cancellable = true)
-    public void protectItem(Slot slot, int slotId, int buttonNum, ContainerInput containerInput, CallbackInfo ci){
+
+    @Inject(method = "slotClicked", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/gui/screens/inventory/AbstractContainerScreen;onMouseClickAction(Lnet/minecraft/world/inventory/Slot;Lnet/minecraft/world/inventory/ContainerInput;)V"), cancellable = true)
+    public void protectItem(Slot slot, int slotId, int buttonNum, ContainerInput containerInput, CallbackInfo ci) {
 
         ItemStack held = menu.getCarried();
 
@@ -92,12 +93,14 @@ public abstract class AbstractContainerScreenMixin<T extends AbstractContainerMe
         }
     }
 
-    @Inject(method = "slotClicked", at=@At("HEAD"))
+    @Inject(method = "slotClicked", at = @At("HEAD"), cancellable = true)
     public void slotClicked(Slot slot, int slotId, int buttonNum, ContainerInput containerInput, CallbackInfo ci) {
-        Events.ON_SLOT_CLICKED.invoke(slotEvent -> slotEvent.onSlot(slot, buttonNum, containerInput));
+        if (Events.ON_SLOT_CLICKED.invoke(slotEvent -> slotEvent.onSlot(slot, slotId, buttonNum, containerInput))) {
+            ci.cancel();
+        }
     }
 
-    @Inject(method = "init", at=@At("HEAD"))
+    @Inject(method = "init", at = @At("HEAD"))
     public void init(CallbackInfo ci) {
         Events.ON_SCREEN.invoke(screenEvent -> screenEvent.onScreen(this));
     }
diff --git a/src/main/java/blade/addon/utils/config/Config.java b/src/main/java/blade/addon/utils/config/Config.java
index d83c73d..400d190 100644
--- a/src/main/java/blade/addon/utils/config/Config.java
+++ b/src/main/java/blade/addon/utils/config/Config.java
@@ -143,6 +143,7 @@ public class Config {
         dungeons.add(new ConfigBool(Component.literal("Draw boss health numbers"), () -> Dungeons.bossHealthNumbers, bool -> Dungeons.bossHealthNumbers = bool));
         dungeons.add(new ConfigBool(Component.literal("Blood notifier on first 4 spawned"), () -> Dungeons.alertBloodSpawns, bool -> Dungeons.alertBloodSpawns = bool));
         dungeons.add(new ConfigBool(Component.literal("Blood notifier on all classes"), () -> Dungeons.alertBloodForAllClasses, bool -> Dungeons.alertBloodForAllClasses = bool));
+        dungeons.add(new ConfigBool(Component.literal("Block \"convert pet to item\""), () -> Dungeons.blockRemovingPet, bool -> Dungeons.blockRemovingPet = bool));
         return dungeons;
     }
 
diff --git a/src/main/java/blade/addon/utils/config/values/Dungeons.java b/src/main/java/blade/addon/utils/config/values/Dungeons.java
index 1dc0333..3ea443a 100644
--- a/src/main/java/blade/addon/utils/config/values/Dungeons.java
+++ b/src/main/java/blade/addon/utils/config/values/Dungeons.java
@@ -154,4 +154,7 @@ public class Dungeons {
 
     @ConfigValue
     public static String deathMessage = "";
+
+    @ConfigValue
+    public static boolean blockRemovingPet = false;
 }
diff --git a/src/main/java/blade/addon/utils/events/interfaces/SlotClickEvent.java b/src/main/java/blade/addon/utils/events/interfaces/SlotClickEvent.java
index c7235a3..a4de6f8 100644
--- a/src/main/java/blade/addon/utils/events/interfaces/SlotClickEvent.java
+++ b/src/main/java/blade/addon/utils/events/interfaces/SlotClickEvent.java
@@ -4,5 +4,5 @@ import net.minecraft.world.inventory.ContainerInput;
 import net.minecraft.world.inventory.Slot;
 
 public interface SlotClickEvent {
-    boolean onSlot(Slot slot, int button, ContainerInput containerInput);
+    boolean onSlot(Slot slot, int slotId, int button, ContainerInput containerInput);
 }
- added shift click to only copy one lineGabriel Ellnebrand · 2026-07-15 · f808046
查看变更
src/main/java/blade/addon/mixin/ChatScreenMixin.java | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/main/java/blade/addon/mixin/ChatScreenMixin.java b/src/main/java/blade/addon/mixin/ChatScreenMixin.java
index e9677d5..d3f4ccf 100644
--- a/src/main/java/blade/addon/mixin/ChatScreenMixin.java
+++ b/src/main/java/blade/addon/mixin/ChatScreenMixin.java
@@ -43,7 +43,7 @@ public class ChatScreenMixin {
         if (index < 0 || index >= messages.size()) return;
 
         String string;
-        if (ExtraOptions.copyLineOnly) {
+        if (ExtraOptions.copyLineOnly || click.hasShiftDown()) {
             GuiMessage.Line msg = messages.get(index);
             string = TextUtil.orderedTextToString(msg.content());
         } else {
- fixed chat scale breaking copy chat - changed blaze name tag hider to only work in dungeonsGabriel Ellnebrand · 2026-07-15 · 5583976
查看变更
src/main/java/blade/addon/mixin/ChatScreenMixin.java     | 6 ++++--
 src/main/java/blade/addon/mixin/EntityRendererMixin.java | 2 +-
 2 files changed, 5 insertions(+), 3 deletions(-)
diff --git a/src/main/java/blade/addon/mixin/ChatScreenMixin.java b/src/main/java/blade/addon/mixin/ChatScreenMixin.java
index 46188d7..e9677d5 100644
--- a/src/main/java/blade/addon/mixin/ChatScreenMixin.java
+++ b/src/main/java/blade/addon/mixin/ChatScreenMixin.java
@@ -34,8 +34,10 @@ public class ChatScreenMixin {
 
         if (hudAccessor.width() + 4 < click.x()) return;
 
-        final int n = (int) (9 * (mc.options.chatLineSpacing().get() + 1.0));
-        int index = (int) (Mth.floor((mc.getWindow().getGuiScaledHeight() - 40) / hudAccessor.scale()) - click.y()) / n + hudAccessor.getScrolledLines();
+        final int n = (int) (9 * (mc.options.chatLineSpacing().get() + 1.0) * hudAccessor.scale());
+        int index = (int) (Mth.floor(mc.getWindow().getGuiScaledHeight() - 40) - click.y()) / n + hudAccessor.getScrolledLines();
+
+        System.out.println(index + " " + hudAccessor.getScrolledLines());
 
         List<GuiMessage.Line> messages = hudAccessor.getVisibleMessages();
         if (index < 0 || index >= messages.size()) return;
diff --git a/src/main/java/blade/addon/mixin/EntityRendererMixin.java b/src/main/java/blade/addon/mixin/EntityRendererMixin.java
index eb183b6..f710caf 100644
--- a/src/main/java/blade/addon/mixin/EntityRendererMixin.java
+++ b/src/main/java/blade/addon/mixin/EntityRendererMixin.java
@@ -21,7 +21,7 @@ public class EntityRendererMixin<T extends Entity, S extends EntityRenderState>
     @Inject(method = "extractRenderState", at = @At("TAIL"))
     public void hideFire(T entity, S state, float tickProgress, CallbackInfo ci) {
 
-        if (Dungeons.hideBlazeNameTag && state.nameTag != null) {
+        if (Dungeons.hideBlazeNameTag && state.nameTag != null && Location.inDungeon()) {
             if (state.nameTag.getString().contains("Blaze")) {
                 state.nameTag = null;
             }
- mightve improved kicked detectionGabriel Ellnebrand · 2026-07-15 · 3af05f7
查看变更
src/main/java/blade/addon/features/other/KickedTimer.java | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/main/java/blade/addon/features/other/KickedTimer.java b/src/main/java/blade/addon/features/other/KickedTimer.java
index 66bb91c..439b3db 100644
--- a/src/main/java/blade/addon/features/other/KickedTimer.java
+++ b/src/main/java/blade/addon/features/other/KickedTimer.java
@@ -17,7 +17,7 @@ public class KickedTimer {
             String string = text.getString();
             if (string == null) return false;
 
-            if (string.equals("You were kicked while joining that server!")) {
+            if (string.equals("You were kicked while joining that server!") || string.equals("You are no longer allowed to access this instance!")) {
                 kickedTime = System.currentTimeMillis();
                 isKicked = true;
             }
- fixed loadouts breaking pet displayGabriel Ellnebrand · 2026-07-15 · ca380f9
查看变更
src/main/java/blade/addon/features/other/SelectedPet.java                                      | 33 +++++++++++++++++++++++++++++++++
 src/main/java/blade/addon/mixin/{HandledScreenMixin.java => AbstractContainerScreenMixin.java} | 15 +++++++++++++--
 src/main/java/blade/addon/mixin/MinecraftMixin.java                                            |  6 ++++++
 src/main/java/blade/addon/utils/data/ItemUtil.java                                             | 28 +++++++++++++++++++++++-----
 src/main/java/blade/addon/utils/events/Events.java                                             | 27 ++++++---------------------
 src/main/java/blade/addon/utils/events/interfaces/ScreenEvent.java                             |  7 +++++++
 src/main/java/blade/addon/utils/events/interfaces/SlotClickEvent.java                          |  8 ++++++++
 src/main/resources/blade-addons.mixins.json                                                    |  2 +-
 8 files changed, 97 insertions(+), 29 deletions(-)
diff --git a/src/main/java/blade/addon/features/other/SelectedPet.java b/src/main/java/blade/addon/features/other/SelectedPet.java
index 31de17e..0276e54 100644
--- a/src/main/java/blade/addon/features/other/SelectedPet.java
+++ b/src/main/java/blade/addon/features/other/SelectedPet.java
@@ -3,6 +3,7 @@ package blade.addon.features.other;
 import blade.addon.utils.Constants;
 import blade.addon.utils.Misc;
 import blade.addon.utils.config.values.ExtraOptions;
+import blade.addon.utils.data.ItemUtil;
 import blade.addon.utils.debug.Debug;
 import blade.addon.utils.events.Events;
 import blade.addon.utils.rendering.RenderUtils;
@@ -44,6 +45,8 @@ public class SelectedPet {
     private static Identifier spriteId = null;
     private static int tick = 0;
 
+    private static boolean inLoadout = false;
+
     public static void init() {
         Events.ON_GAME_MESSAGE.register(message -> {
             String string = message.getString();
@@ -98,6 +101,36 @@ public class SelectedPet {
             return false;
         });
 
+        Events.ON_SCREEN.register(screen -> {
+            if (screen == null) {
+                inLoadout = false;
+                return false;
+            }
+
+            Component title = screen.getTitle();
+            inLoadout = title.getString().contains(") Loadouts");
+
+            return false;
+        });
+
+        Events.ON_SLOT_CLICKED.register((slot, button,clickType) -> {
+            if (!inLoadout || button != 0) return false;
+
+            Component component = ItemUtil.findLore(slot.getItem(), "Pet: ");
+            if (component == null) return false;
+
+            String string = component.getString();
+            Matcher matcher = TAB_PET_PATTERN.matcher(string);
+            if (matcher.find()) {
+                String petName = matcher.group(1).replace(" ✦", "");
+                Style style = getStyle(component, petName);
+                summonPet(petName, Component.literal(petName).setStyle(style), getLevel(string), false);
+            }
+
+
+            return false;
+        });
+
         ClientTickEvents.END_LEVEL_TICK.register((world -> {
             tick = Math.max(tick - 1, 0);
         }));
diff --git a/src/main/java/blade/addon/mixin/HandledScreenMixin.java b/src/main/java/blade/addon/mixin/AbstractContainerScreenMixin.java
similarity index 85%
rename from src/main/java/blade/addon/mixin/HandledScreenMixin.java
rename to src/main/java/blade/addon/mixin/AbstractContainerScreenMixin.java
index e750f3e..c128137 100644
--- a/src/main/java/blade/addon/mixin/HandledScreenMixin.java
+++ b/src/main/java/blade/addon/mixin/AbstractContainerScreenMixin.java
@@ -2,6 +2,7 @@ package blade.addon.mixin;
 
 import blade.addon.features.item.ProtectItem;
 import blade.addon.features.other.SearchBar;
+import blade.addon.utils.events.Events;
 import blade.addon.utils.rendering.DrawEvents;
 import net.minecraft.client.gui.GuiGraphicsExtractor;
 import net.minecraft.client.gui.screens.Screen;
@@ -23,7 +24,7 @@ import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
 import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
 
 @Mixin(AbstractContainerScreen.class)
-public abstract class HandledScreenMixin<T extends AbstractContainerMenu> extends Screen {
+public abstract class AbstractContainerScreenMixin<T extends AbstractContainerMenu> extends Screen {
 
     @Shadow
     @Final
@@ -31,7 +32,7 @@ public abstract class HandledScreenMixin<T extends AbstractContainerMenu> extend
     @Unique
     private static final int INVALID_SLOT_ID = -999;
 
-    protected HandledScreenMixin(Component title) {
+    protected AbstractContainerScreenMixin(Component title) {
         super(title);
     }
 
@@ -90,4 +91,14 @@ public abstract class HandledScreenMixin<T extends AbstractContainerMenu> extend
             }
         }
     }
+
+    @Inject(method = "slotClicked", at=@At("HEAD"))
+    public void slotClicked(Slot slot, int slotId, int buttonNum, ContainerInput containerInput, CallbackInfo ci) {
+        Events.ON_SLOT_CLICKED.invoke(slotEvent -> slotEvent.onSlot(slot, buttonNum, containerInput));
+    }
+
+    @Inject(method = "init", at=@At("HEAD"))
+    public void init(CallbackInfo ci) {
+        Events.ON_SCREEN.invoke(screenEvent -> screenEvent.onScreen(this));
+    }
 }
diff --git a/src/main/java/blade/addon/mixin/MinecraftMixin.java b/src/main/java/blade/addon/mixin/MinecraftMixin.java
index 78a64f8..8f4206f 100644
--- a/src/main/java/blade/addon/mixin/MinecraftMixin.java
+++ b/src/main/java/blade/addon/mixin/MinecraftMixin.java
@@ -5,6 +5,7 @@ import blade.addon.utils.events.Events;
 import blade.addon.utils.events.interfaces.WorldEvent;
 import com.llamalad7.mixinextras.sugar.Local;
 import net.minecraft.client.Minecraft;
+import net.minecraft.client.gui.screens.Screen;
 import net.minecraft.client.multiplayer.ClientLevel;
 import net.minecraft.world.item.ItemStack;
 import net.minecraft.world.phys.BlockHitResult;
@@ -42,4 +43,9 @@ public class MinecraftMixin {
     private void onWorld(ClientLevel world, CallbackInfo ci) {
         Events.ON_WORLD_CHANGE.invoke(WorldEvent::onWorldSwap);
     }
+
+    @Inject(method = "setScreen", at=@At("HEAD"))
+    public void onClose(Screen screen, CallbackInfo ci) {
+        if (screen == null) Events.ON_SCREEN.invoke(screenEvent -> screenEvent.onScreen(null));
+    }
 }
diff --git a/src/main/java/blade/addon/utils/data/ItemUtil.java b/src/main/java/blade/addon/utils/data/ItemUtil.java
index a75402c..25b5bf8 100644
--- a/src/main/java/blade/addon/utils/data/ItemUtil.java
+++ b/src/main/java/blade/addon/utils/data/ItemUtil.java
@@ -43,20 +43,38 @@ public class ItemUtil {
     }
 
     public static boolean containsLore(ItemStack item, String contain) {
-        if (item ==null) return false;
+        List<Component> lines = getLore(item);
+        if (lines == null) return false;
+
+        for (Component line : lines.reversed()) {
+            String string = line.getString();
+            if (string.contains(contain)) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    public static List<Component> getLore(ItemStack item) {
+        if (item ==null) return null;
         ItemLore lore = item.get(DataComponents.LORE);
-        if (lore == null) return false;
+        if (lore == null) return null;
 
         List<Component> lines = lore.lines();
-        if (lines.isEmpty()) return false;
+        return lines;
+    }
+
+    public static Component findLore(ItemStack item, String contain) {
+        List<Component> lines = getLore(item);
+        if (lines == null) return null;
 
         for (Component line : lines.reversed()) {
             String string = line.getString();
             if (string.contains(contain)) {
-                return true;
+                return line;
             }
         }
-        return false;
+        return null;
     }
 
     public static boolean containsIgnoreCaseLore(ItemStack item, String contain) {
diff --git a/src/main/java/blade/addon/utils/events/Events.java b/src/main/java/blade/addon/utils/events/Events.java
index 9e37c3d..7932c11 100644
--- a/src/main/java/blade/addon/utils/events/Events.java
+++ b/src/main/java/blade/addon/utils/events/Events.java
@@ -1,25 +1,6 @@
 package blade.addon.utils.events;
 
-import blade.addon.utils.events.interfaces.BlockEntityEvent;
-import blade.addon.utils.events.interfaces.BlockInteractionEvent;
-import blade.addon.utils.events.interfaces.EntityEvent;
-import blade.addon.utils.events.interfaces.GameMessageEvent;
-import blade.addon.utils.events.interfaces.LeapEvent;
-import blade.addon.utils.events.interfaces.LocationChangeEvent;
-import blade.addon.utils.events.interfaces.PacketEvent;
-import blade.addon.utils.events.interfaces.ParticleEvent;
-import blade.addon.utils.events.interfaces.PartyMessageEvent;
-import blade.addon.utils.events.interfaces.PetEvent;
-import blade.addon.utils.events.interfaces.PhaseEvent;
-import blade.addon.utils.events.interfaces.PlaySoundEvent;
-import blade.addon.utils.events.interfaces.PlayerListEvent;
-import blade.addon.utils.events.interfaces.RunEndEvent;
-import blade.addon.utils.events.interfaces.ScoreBoardEvent;
-import blade.addon.utils.events.interfaces.SectionEvent;
-import blade.addon.utils.events.interfaces.ServerTickEvent;
-import blade.addon.utils.events.interfaces.SlotChangeEvent;
-import blade.addon.utils.events.interfaces.TerminalEvent;
-import blade.addon.utils.events.interfaces.WorldEvent;
+import blade.addon.utils.events.interfaces.*;
 
 public class Events {
     public static final EventHandler<ServerTickEvent> ON_SERVER_TICK = new EventHandler<>();
@@ -49,8 +30,12 @@ public class Events {
 
     public static final EventHandler<BlockEntityEvent> ON_BLOCK_ENTITY = new EventHandler<>();
 
-
     public static final EventHandler<ParticleEvent> ON_PARTICLE = new EventHandler<>();
     public static final EventHandler<PacketEvent> ON_PACKET = new EventHandler<>();
 
+    //screen
+    public static final EventHandler<ScreenEvent> ON_SCREEN = new EventHandler<>();
+    public static final EventHandler<SlotClickEvent> ON_SLOT_CLICKED = new EventHandler<>();
+
+
 }
diff --git a/src/main/java/blade/addon/utils/events/interfaces/ScreenEvent.java b/src/main/java/blade/addon/utils/events/interfaces/ScreenEvent.java
new file mode 100644
index 0000000..f2ecdb4
--- /dev/null
+++ b/src/main/java/blade/addon/utils/events/interfaces/ScreenEvent.java
@@ -0,0 +1,7 @@
+package blade.addon.utils.events.interfaces;
+
+import net.minecraft.client.gui.screens.Screen;
+
+public interface ScreenEvent {
+    boolean onScreen(Screen screen);
+}
diff --git a/src/main/java/blade/addon/utils/events/interfaces/SlotClickEvent.java b/src/main/java/blade/addon/utils/events/interfaces/SlotClickEvent.java
new file mode 100644
index 0000000..c7235a3
--- /dev/null
+++ b/src/main/java/blade/addon/utils/events/interfaces/SlotClickEvent.java
@@ -0,0 +1,8 @@
+package blade.addon.utils.events.interfaces;
+
+import net.minecraft.world.inventory.ContainerInput;
+import net.minecraft.world.inventory.Slot;
+
+public interface SlotClickEvent {
+    boolean onSlot(Slot slot, int button, ContainerInput containerInput);
+}
diff --git a/src/main/resources/blade-addons.mixins.json b/src/main/resources/blade-addons.mixins.json
index 82e57af..8a15d2a 100644
--- a/src/main/resources/blade-addons.mixins.json
+++ b/src/main/resources/blade-addons.mixins.json
@@ -29,7 +29,7 @@
     "GuiGraphicsExtractorMixin",
     "EntityRendererMixin",
     "EntityRenderDispatcherMixin",
-    "HandledScreenMixin",
+    "AbstractContainerScreenMixin",
     "HeldItemRendererMixin",
     "GuiMixin",
     "InventoryScreenMixin",
- added missing pet spritesGabriel Ellnebrand · 2026-07-15 · c4408dd
查看变更
gradle.properties                                                                      |   2 +-
 src/main/java/blade/addon/features/other/SelectedPet.java                              |   3 +++
 src/main/resources/assets/blade-addons/textures/gui/sprites/pets/golden-dragon-egg.png | Bin 0 -> 2647 bytes
 src/main/resources/assets/blade-addons/textures/gui/sprites/pets/jade-dragon-egg.png   | Bin 0 -> 2579 bytes
 src/main/resources/assets/blade-addons/textures/gui/sprites/pets/rose-dragon-egg.png   | Bin 0 -> 2193 bytes
 src/main/resources/assets/blade-addons/textures/gui/sprites/pets/seal.png              | Bin 0 -> 5993 bytes
 src/main/resources/assets/blade-addons/textures/gui/sprites/pets/sloth.png             | Bin 0 -> 3976 bytes
 7 files changed, 4 insertions(+), 1 deletion(-)
diff --git a/gradle.properties b/gradle.properties
index fea06dd..46e3427 100644
--- a/gradle.properties
+++ b/gradle.properties
@@ -9,7 +9,7 @@ minecraft_version=26.1.2
 loader_version=0.19.3
 loom_version=1.17-SNAPSHOT
 # Mod Properties
-mod_version=2.5.1
+mod_version=2.5.2
 maven_group=blade.addon
 archives_base_name=blade-addons
 # Dependencies
diff --git a/src/main/java/blade/addon/features/other/SelectedPet.java b/src/main/java/blade/addon/features/other/SelectedPet.java
index aacb040..31de17e 100644
--- a/src/main/java/blade/addon/features/other/SelectedPet.java
+++ b/src/main/java/blade/addon/features/other/SelectedPet.java
@@ -23,6 +23,9 @@ import java.util.regex.Pattern;
 
 public class SelectedPet {
 
+    //https://hypixelskyblock.minecraft.wiki/w/Pets
+    //https://cloudconvert.com/to-png
+
     private static final int COLOR_OFFSET = 2;
 
     private static final Pattern TAB_PET_PATTERN = Pattern.compile(" \\[Lvl \\d+] (?:\\[[^\\]]+\\] )?(.+)");
diff --git a/src/main/resources/assets/blade-addons/textures/gui/sprites/pets/golden-dragon-egg.png b/src/main/resources/assets/blade-addons/textures/gui/sprites/pets/golden-dragon-egg.png
new file mode 100644
index 0000000..4464ada
Binary files /dev/null and b/src/main/resources/assets/blade-addons/textures/gui/sprites/pets/golden-dragon-egg.png differ
diff --git a/src/main/resources/assets/blade-addons/textures/gui/sprites/pets/jade-dragon-egg.png b/src/main/resources/assets/blade-addons/textures/gui/sprites/pets/jade-dragon-egg.png
new file mode 100644
index 0000000..f2cd6e3
Binary files /dev/null and b/src/main/resources/assets/blade-addons/textures/gui/sprites/pets/jade-dragon-egg.png differ
diff --git a/src/main/resources/assets/blade-addons/textures/gui/sprites/pets/rose-dragon-egg.png b/src/main/resources/assets/blade-addons/textures/gui/sprites/pets/rose-dragon-egg.png
new file mode 100644
index 0000000..09cec6a
Binary files /dev/null and b/src/main/resources/assets/blade-addons/textures/gui/sprites/pets/rose-dragon-egg.png differ
diff --git a/src/main/resources/assets/blade-addons/textures/gui/sprites/pets/seal.png b/src/main/resources/assets/blade-addons/textures/gui/sprites/pets/seal.png
new file mode 100644
index 0000000..ea4a459
Binary files /dev/null and b/src/main/resources/assets/blade-addons/textures/gui/sprites/pets/seal.png differ
diff --git a/src/main/resources/assets/blade-addons/textures/gui/sprites/pets/sloth.png b/src/main/resources/assets/blade-addons/textures/gui/sprites/pets/sloth.png
new file mode 100644
index 0000000..08c2002
Binary files /dev/null and b/src/main/resources/assets/blade-addons/textures/gui/sprites/pets/sloth.png differ
- upped versionGabriel Ellnebrand · 2026-07-08 · 67f0848
查看变更
gradle.properties | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/gradle.properties b/gradle.properties
index 78ec16a..fea06dd 100644
--- a/gradle.properties
+++ b/gradle.properties
@@ -9,7 +9,7 @@ minecraft_version=26.1.2
 loader_version=0.19.3
 loom_version=1.17-SNAPSHOT
 # Mod Properties
-mod_version=2.5.0
+mod_version=2.5.1
 maven_group=blade.addon
 archives_base_name=blade-addons
 # Dependencies
- fixed admins doing stupid shitGabriel Ellnebrand · 2026-07-08 · 07d10d8
查看变更
src/main/java/blade/addon/utils/dungeon/Phase.java         | 2 +-
 src/main/java/blade/addon/utils/rendering/RenderUtils.java | 4 +---
 2 files changed, 2 insertions(+), 4 deletions(-)
diff --git a/src/main/java/blade/addon/utils/dungeon/Phase.java b/src/main/java/blade/addon/utils/dungeon/Phase.java
index 5245108..26ca223 100644
--- a/src/main/java/blade/addon/utils/dungeon/Phase.java
+++ b/src/main/java/blade/addon/utils/dungeon/Phase.java
@@ -21,7 +21,7 @@ import net.minecraft.network.chat.Component;
 public class Phase {
 
     private static final Pattern END_PATTERN = Pattern.compile("^\\s*☠ Defeated (.+) in 0?([\\dhms ]+)\\s*(\\(NEW RECORD!\\))?$");
-    private static final Pattern SEARCH_PATTERN = Pattern.compile("^ ⏣ The Catacombs .*$");
+    private static final Pattern SEARCH_PATTERN = Pattern.compile("The Catacombs .*$");
 
     private static final Split DUMMY_SPLIT = new Split("test split", "if this is called idk", "if this is called idk", 43690);
 
diff --git a/src/main/java/blade/addon/utils/rendering/RenderUtils.java b/src/main/java/blade/addon/utils/rendering/RenderUtils.java
index 73543f7..3c34143 100644
--- a/src/main/java/blade/addon/utils/rendering/RenderUtils.java
+++ b/src/main/java/blade/addon/utils/rendering/RenderUtils.java
@@ -103,9 +103,7 @@ public class RenderUtils {
 
         float halfWidth = textRenderer.width(text.getString()) / 2f;
 
-
-        //context.commandQueue().submitText(matrices, -halfWidth, 0, text.getVisualOrderText(), true, Font.DisplayMode.SEE_THROUGH, 15728880, 0xffffffff, 0, 0);
-        textRenderer.drawInBatch(text, -halfWidth, 0, 15728880, true, matrices.last().pose(), context.bufferSource(), Font.DisplayMode.SEE_THROUGH, 0, 0xffffffff);
+        textRenderer.drawInBatch(text, -halfWidth, 0, 0xffffffff, true, matrices.last().pose(), context.bufferSource(), Font.DisplayMode.SEE_THROUGH, 0, 15728880);
         matrices.popPose();
     }
 
- bumped versionGabriel Ellnebrand · 2026-06-26 · e5fd841
查看变更
gradle.properties | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/gradle.properties b/gradle.properties
index 71e11db..78ec16a 100644
--- a/gradle.properties
+++ b/gradle.properties
@@ -9,7 +9,7 @@ minecraft_version=26.1.2
 loader_version=0.19.3
 loom_version=1.17-SNAPSHOT
 # Mod Properties
-mod_version=2.4.1
+mod_version=2.5.0
 maven_group=blade.addon
 archives_base_name=blade-addons
 # Dependencies
- fixed readmeGabriel Ellnebrand · 2026-06-26 · 4ed0854
查看变更
README.md | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/README.md b/README.md
index fc622ed..16464d0 100644
--- a/README.md
+++ b/README.md
@@ -4,7 +4,7 @@ A dungeons mod made to port dungeons features to the newer Minecraft versions. H
 his amazing mods like Valley addons.
 
 # Current Version
-1.21.10
+26.1.2
 
 # Dependencies
 - Hypixel Mod API https://modrinth.com/mod/hypixel-mod-api
- fixed player heads crashing your gameGabriel Ellnebrand · 2026-06-26 · c6d28f9
查看变更
src/main/java/blade/addon/mixin/GuiGraphicsExtractorMixin.java | 5 ++---
 src/main/java/blade/addon/utils/config/Config.java             | 2 +-
 2 files changed, 3 insertions(+), 4 deletions(-)
diff --git a/src/main/java/blade/addon/mixin/GuiGraphicsExtractorMixin.java b/src/main/java/blade/addon/mixin/GuiGraphicsExtractorMixin.java
index 509b5ef..aed8572 100644
--- a/src/main/java/blade/addon/mixin/GuiGraphicsExtractorMixin.java
+++ b/src/main/java/blade/addon/mixin/GuiGraphicsExtractorMixin.java
@@ -5,7 +5,6 @@ import net.minecraft.client.gui.GuiGraphicsExtractor;
 import net.minecraft.world.entity.LivingEntity;
 import net.minecraft.world.item.ItemStack;
 import net.minecraft.world.item.Items;
-import net.minecraft.world.level.Level;
 import org.joml.Matrix3x2fStack;
 import org.spongepowered.asm.mixin.Final;
 import org.spongepowered.asm.mixin.Mixin;
@@ -40,8 +39,8 @@ public class GuiGraphicsExtractorMixin {
         }
     }
 
-    @Inject(method = "item(Lnet/minecraft/world/entity/LivingEntity;Lnet/minecraft/world/level/Level;Lnet/minecraft/world/item/ItemStack;III)V", at=@At("TAIL"))
-    private void scaleDown(LivingEntity entity, Level world, ItemStack stack, int x, int y, int seed, CallbackInfo ci) {
+    @Inject(method = "item(Lnet/minecraft/world/entity/LivingEntity;Lnet/minecraft/world/item/ItemStack;III)V", at=@At("TAIL"))
+    private void scaleDown(LivingEntity owner, ItemStack stack, int x, int y, int seed, CallbackInfo ci) {
         if (Visual.oldPlayerHead && stack.getItem() == Items.PLAYER_HEAD) {
             pose.popMatrix();
         }
diff --git a/src/main/java/blade/addon/utils/config/Config.java b/src/main/java/blade/addon/utils/config/Config.java
index f60f09a..d83c73d 100644
--- a/src/main/java/blade/addon/utils/config/Config.java
+++ b/src/main/java/blade/addon/utils/config/Config.java
@@ -78,7 +78,7 @@ public class Config {
         ConfigSection deathMessage = new ConfigSection(Component.literal("Death message"));
         deathMessage.add(new ConfigBool(Component.literal("Send death message"), () -> Dungeons.sendDeathMessage, bool -> Dungeons.sendDeathMessage = bool));
         deathMessage.add(new ConfigString(Component.literal("Message sent (<player> replaces with the players name) "), () -> Dungeons.deathMessage, str -> Dungeons.deathMessage = str));
-        dungeons.add(deathMessage);
+        //dungeons.add(deathMessage);
 
         ConfigSection invincibility = new ConfigSection(Component.literal("Invincibility Timer"));
         invincibility.add(new ConfigBool(Component.literal("Enable invincibility display"), () -> Dungeons.displayInvincibilityTimer, bool -> Dungeons.displayInvincibilityTimer = bool));
- 26.1.2 port (first launchable version)Gabriel Ellnebrand · 2026-06-25 · 387e6d1
查看变更
.github/workflows/build.yml                                                               |  12 +++---
 build.gradle                                                                              | 126 ++++++++++++++++++++++++++++----------------------------------
 gradle.properties                                                                         |  20 ++++------
 gradle/wrapper/gradle-wrapper.properties                                                  |   5 +--
 src/main/java/blade/addon/features/dungeon/BloodNotifier.java                             |   4 +-
 src/main/java/blade/addon/features/dungeon/ChestCounter.java                              |  11 +++---
 src/main/java/blade/addon/features/dungeon/ItemHighlight.java                             |   4 +-
 src/main/java/blade/addon/features/dungeon/KeyNotifier.java                               |   9 +++--
 src/main/java/blade/addon/features/dungeon/QuizTimer.java                                 |   6 +--
 src/main/java/blade/addon/features/dungeon/RunStartValidator.java                         |  13 ++++---
 src/main/java/blade/addon/features/dungeon/SecretSpawnTimer.java                          |   9 +++--
 src/main/java/blade/addon/features/dungeon/WarpCooldown.java                              |   5 ++-
 src/main/java/blade/addon/features/dungeon/f7/BossWaypoints.java                          |   6 +--
 src/main/java/blade/addon/features/dungeon/f7/RelicTimer.java                             |  13 ++++---
 src/main/java/blade/addon/features/dungeon/f7/dragons/DragSpawnTimer.java                 |   4 +-
 src/main/java/blade/addon/features/dungeon/f7/dragons/DragonHealth.java                   |   7 ++--
 src/main/java/blade/addon/features/dungeon/f7/dragons/DragonTracer.java                   |   4 +-
 src/main/java/blade/addon/features/dungeon/f7/invincibility/InvincibilityDuration.java    |   4 +-
 src/main/java/blade/addon/features/dungeon/f7/invincibility/InvincibilityTimer.java       |  23 ++++++------
 src/main/java/blade/addon/features/dungeon/f7/invincibility/MaskHighlight.java            |   4 +-
 src/main/java/blade/addon/features/dungeon/f7/location/LocationNotifier.java              |   5 ++-
 src/main/java/blade/addon/features/dungeon/f7/location/PositionMessages.java              |   5 ++-
 src/main/java/blade/addon/features/dungeon/f7/maxor/CrystalSpawn.java                     |  11 +++---
 src/main/java/blade/addon/features/dungeon/f7/maxor/MaxorStun.java                        |   4 +-
 src/main/java/blade/addon/features/dungeon/f7/maxor/MaxorTickTimer.java                   |   4 +-
 src/main/java/blade/addon/features/dungeon/f7/storm/DistanceToLedge.java                  |   4 +-
 src/main/java/blade/addon/features/dungeon/f7/storm/PillarExplode.java                    |   6 +--
 src/main/java/blade/addon/features/dungeon/f7/storm/StormTickTimer.java                   |   9 +++--
 src/main/java/blade/addon/features/dungeon/f7/terms/CurrentSection.java                   |   4 +-
 src/main/java/blade/addon/features/dungeon/f7/terms/DeviceNotifier.java                   |   4 +-
 src/main/java/blade/addon/features/dungeon/f7/terms/GoldorTickTimer.java                  |   4 +-
 src/main/java/blade/addon/features/dungeon/f7/terms/LeapNotification.java                 |   5 ++-
 src/main/java/blade/addon/features/dungeon/f7/terms/MelodyWarning.java                    |  11 +++---
 src/main/java/blade/addon/features/dungeon/f7/terms/SectionCompletion.java                |   4 +-
 src/main/java/blade/addon/features/dungeon/f7/terms/SectionProgress.java                  |   4 +-
 src/main/java/blade/addon/features/dungeon/f7/terms/TermStartTimer.java                   |   4 +-
 src/main/java/blade/addon/features/filter/FilterEntry.java                                |  15 ++++----
 src/main/java/blade/addon/features/filter/FilterList.java                                 |  13 ++++---
 src/main/java/blade/addon/features/highlight/BatHighlight.java                            |   6 +--
 src/main/java/blade/addon/features/highlight/MimicHighlight.java                          |   7 ++--
 src/main/java/blade/addon/features/highlight/MobHighlight.java                            |   7 ++--
 src/main/java/blade/addon/features/highlight/SheepHighlight.java                          |   7 ++--
 src/main/java/blade/addon/features/highlight/TeammateHighlight.java                       |   5 ++-
 src/main/java/blade/addon/features/highlight/WitherHighlight.java                         |   7 ++--
 src/main/java/blade/addon/features/item/HeldItemToolTip.java                              |   4 +-
 src/main/java/blade/addon/features/item/ItemRarityHighlight.java                          |   7 ++--
 src/main/java/blade/addon/features/item/ProtectItem.java                                  |  13 ++++---
 src/main/java/blade/addon/features/item/SelectedPetHighlight.java                         |   7 ++--
 src/main/java/blade/addon/features/item/StarCountHighlight.java                           |   6 +--
 src/main/java/blade/addon/features/notifications/NotificationEntry.java                   |  16 ++++----
 src/main/java/blade/addon/features/notifications/NotificationList.java                    |  17 ++++-----
 src/main/java/blade/addon/features/notifications/Notifications.java                       |   5 ++-
 src/main/java/blade/addon/features/other/ArrowSwapper.java                                |   9 +++--
 src/main/java/blade/addon/features/other/InventoryButton.java                             |  17 +++++----
 src/main/java/blade/addon/features/other/KickedTimer.java                                 |   4 +-
 src/main/java/blade/addon/features/other/PracticeSS.java                                  |   4 +-
 src/main/java/blade/addon/features/other/RagDisplay.java                                  |   6 +--
 src/main/java/blade/addon/features/other/ReaperDisplay.java                               |   4 +-
 src/main/java/blade/addon/features/other/SearchBar.java                                   |   8 ++--
 src/main/java/blade/addon/features/other/SelectedPet.java                                 |  13 ++++---
 src/main/java/blade/addon/features/sound/TubaSound.java                                   |   2 +-
 src/main/java/blade/addon/mixin/BossHealthOverlayMixin.java                               |   2 +-
 src/main/java/blade/addon/mixin/ChatComponentMixin.java                                   |   2 +-
 src/main/java/blade/addon/mixin/ChatScreenMixin.java                                      |   2 +-
 src/main/java/blade/addon/mixin/EffectsInInventoryMixin.java                              |  10 ++---
 src/main/java/blade/addon/mixin/{GuiGraphicsMixin.java => GuiGraphicsExtractorMixin.java} |  14 +++----
 src/main/java/blade/addon/mixin/GuiMixin.java                                             |  22 +++++------
 src/main/java/blade/addon/mixin/HandledScreenMixin.java                                   |  30 +++++++--------
 src/main/java/blade/addon/mixin/InventoryScreenMixin.java                                 |  10 ++---
 src/main/java/blade/addon/mixin/RecipeBookComponentMixin.java                             |   6 +--
 src/main/java/blade/addon/utils/Commands.java                                             |  68 ++++++++++++++++-----------------
 src/main/java/blade/addon/utils/Keybinds.java                                             |  14 +++----
 src/main/java/blade/addon/utils/Misc.java                                                 |   2 +-
 src/main/java/blade/addon/utils/config/components/CombinedScreenNotifications.java        |   8 ++--
 src/main/java/blade/addon/utils/config/components/CombinedTickTimer.java                  |   6 +--
 src/main/java/blade/addon/utils/debug/Debug.java                                          |  44 +++++++++++-----------
 src/main/java/blade/addon/utils/dungeon/Section.java                                      |  13 ++++---
 src/main/java/blade/addon/utils/dungeon/Split.java                                        |   8 ++--
 src/main/java/blade/addon/utils/rendering/RenderHandler.java                              |  12 +++---
 src/main/java/blade/addon/utils/rendering/RenderPipelines.java                            |  13 ++++---
 src/main/java/blade/addon/utils/rendering/RenderUtils.java                                |  56 ++++++++++++++--------------
 src/main/java/blade/addon/utils/rendering/RenderingEvent.java                             |   5 +--
 src/main/java/blade/addon/utils/rendering/RenderingEvents.java                            |  14 +++----
 src/main/java/blade/addon/utils/rendering/drawevents/SlotEvent.java                       |   4 +-
 src/main/resources/blade-addons.mixins.json                                               |   2 +-
 src/main/resources/fabric.mod.json                                                        |   6 +--
 86 files changed, 486 insertions(+), 473 deletions(-)
diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml
index 8fc3827..524fb1b 100644
--- a/.github/workflows/build.yml
+++ b/.github/workflows/build.yml
@@ -11,20 +11,20 @@ jobs:
     runs-on: ubuntu-24.04
     steps:
       - name: checkout repository
-        uses: actions/checkout@v4
+        uses: actions/checkout@v6
       - name: validate gradle wrapper
-        uses: gradle/actions/wrapper-validation@v4
+        uses: gradle/actions/wrapper-validation@v6
       - name: setup jdk
-        uses: actions/setup-java@v4
+        uses: actions/setup-java@v5
         with:
-          java-version: '21'
+          java-version: '25'
           distribution: 'microsoft'
       - name: make gradle wrapper executable
         run: chmod +x ./gradlew
       - name: build
         run: ./gradlew build
       - name: capture build artifacts
-        uses: actions/upload-artifact@v4
+        uses: actions/upload-artifact@v7
         with:
           name: Artifacts
-          path: build/libs/
\ No newline at end of file
+          path: build/libs/
diff --git a/build.gradle b/build.gradle
index 3aa226d..102c239 100644
--- a/build.gradle
+++ b/build.gradle
@@ -1,17 +1,17 @@
 plugins {
-	id 'fabric-loom' version "${loom_version}"
-	id 'maven-publish'
+    id 'net.fabricmc.fabric-loom' version "${loom_version}"
+    id 'maven-publish'
 }
 
 version = project.mod_version
 group = project.maven_group
 
 base {
-	archivesName = project.archives_base_name
+    archivesName = project.archives_base_name
 }
 
 java {
-    toolchain.languageVersion = JavaLanguageVersion.of(21)
+    toolchain.languageVersion = JavaLanguageVersion.of(25)
     // Loom will automatically attach sourcesJar to a RemapSourcesJar task and to the "build" task
     // if it is present.
     // If you remove this line, sources will not be generated.
@@ -21,10 +21,11 @@ java {
 loom {
     runConfigs.named("client") {
         setIdeConfigGenerated(true)
-        vmArgs.add("-Dmixin.debug.export=true")
-        vmArgs.add("-Ddevauth.enabled=true")
-        vmArgs.add("-Ddevauth.account=main")
-        vmArgs.add("-XX:+AllowEnhancedClassRedefinition")
+        vmArgs("-XX:+IgnoreUnrecognizedVMOptions",
+                "-Dmixin.debug.export=true",
+                "-Ddevauth.enabled=true",
+                "-Ddevauth.account=main",
+                "-XX:+AllowEnhancedClassRedefinition")
     }
 
     runConfigs.named("server") {
@@ -32,94 +33,81 @@ loom {
     }
 }
 
+repositories {
 
+    maven { url = "https://pkgs.dev.azure.com/djtheredstoner/DevAuth/_packaging/public/maven/v1" }
+    maven { url = 'https://jitpack.io' }
+    maven {
+        name = "Terraformers"
+        url = "https://maven.terraformersmc.com/"
+    }
+    maven { url = 'https://repo.hypixel.net/repository/Hypixel/' }
 
-repositories {
-	// Add repositories to retrieve artifacts from in here.
-	// You should only use this when depending on other mods because
-	// Loom adds the essential maven repositories to download Minecraft and libraries from automatically.
-	// See https://docs.gradle.org/current/userguide/declaring_repositories.html
-	// for more information about repositories.
-    maven  {url = "https://pkgs.dev.azure.com/djtheredstoner/DevAuth/_packaging/public/maven/v1" }
-
-	repositories {
-		mavenCentral()
-		maven { url 'https://jitpack.io' }
-	}
-
-	maven {
-		name = "Terraformers"
-		url = "https://maven.terraformersmc.com/"
-	}
-
-	repositories {
-		maven { url = 'https://repo.hypixel.net/repository/Hypixel/' }
-	}
 }
 
 dependencies {
-	// To change the versions see the gradle.properties file
-	minecraft "com.mojang:minecraft:${project.minecraft_version}"
-	//mappings "net.fabricmc:yarn:${project.yarn_mappings}:v2"
-	mappings loom.officialMojangMappings()
-	modImplementation "net.fabricmc:fabric-loader:${project.loader_version}"
+    // To change the versions see the gradle.properties file
+    minecraft "com.mojang:minecraft:${project.minecraft_version}"
+    //mappings "net.fabricmc:yarn:${project.yarn_mappings}:v2"
+    implementation "net.fabricmc:fabric-loader:${project.loader_version}"
 
-	// Fabric API. This is technically optional, but you probably want it anyway.
-	modImplementation "net.fabricmc.fabric-api:fabric-api:${project.fabric_version}"
-	modImplementation("com.terraformersmc:modmenu:${project.modmenu_version}")
-	include modImplementation("net.hypixel:mod-api:${project.hypixel_api_version}")
-	include modImplementation("com.github.BladeMasterGabe:practical-config:${project.practical_config}")
+    // Fabric API. This is technically optional, but you probably want it anyway.
+    implementation "net.fabricmc.fabric-api:fabric-api:${project.fabric_api_version}"
+    implementation("com.terraformersmc:modmenu:${project.modmenu_version}")
+    include implementation("net.hypixel:mod-api:${project.hypixel_api_version}")
+    include implementation("com.github.BladeMasterGabe:practical-config:${project.practical_config}")
 
-    modRuntimeOnly("me.djtheredstoner:DevAuth-fabric:1.2.2")
+    runtimeOnly("me.djtheredstoner:DevAuth-fabric:${project.dev_auth_version}")
 
 
 }
 
 processResources {
-	inputs.property "version", project.version
+    inputs.property "version", project.version
 
-	filesMatching("fabric.mod.json") {
-		expand "version": inputs.properties.version
-	}
+    filesMatching("fabric.mod.json") {
+        expand "version": inputs.properties.version
+    }
 }
 
 tasks.withType(JavaCompile).configureEach {
-	it.options.release = 21
+    it.options.release = 25
 }
 
 java {
-	// Loom will automatically attach sourcesJar to a RemapSourcesJar task and to the "build" task
-	// if it is present.
-	// If you remove this line, sources will not be generated.
-	withSourcesJar()
+    // Loom will automatically attach sourcesJar to a RemapSourcesJar task and to the "build" task
+    // if it is present.
+    // If you remove this line, sources will not be generated.
+    withSourcesJar()
 
-	sourceCompatibility = JavaVersion.VERSION_21
-	targetCompatibility = JavaVersion.VERSION_21
+    sourceCompatibility = JavaVersion.VERSION_25
+    targetCompatibility = JavaVersion.VERSION_25
 }
 
 jar {
-	inputs.property "archivesName", project.base.archivesName
+    def projectName = project.name
+    inputs.property "projectName", projectName
 
-	from("LICENSE") {
-		rename { "${it}_${inputs.properties.archivesName}"}
-	}
+    from("LICENSE") {
+        rename { "${it}_$projectName"}
+    }
 }
 
 // configure the maven publication
 publishing {
-	publications {
-		create("mavenJava", MavenPublication) {
-			artifactId = project.archives_base_name
-			from components.java
-		}
-	}
-
-	// See https://docs.gradle.org/current/userguide/publishing_maven.html for information on how to set up publishing.
-	repositories {
-		// Add repositories to publish to here.
-		// Notice: This block does NOT have the same function as the block in the top level.
-		// The repositories here will be used for publishing your artifact, not for
-		// retrieving dependencies.
-	}
+    publications {
+        create("mavenJava", MavenPublication) {
+            artifactId = project.archives_base_name
+            from components.java
+        }
+    }
+
+    // See https://docs.gradle.org/current/userguide/publishing_maven.html for information on how to set up publishing.
+    repositories {
+        // Add repositories to publish to here.
+        // Notice: This block does NOT have the same function as the block in the top level.
+        // The repositories here will be used for publishing your artifact, not for
+        // retrieving dependencies.
+    }
 }
 
diff --git a/gradle.properties b/gradle.properties
index 8659370..71e11db 100644
--- a/gradle.properties
+++ b/gradle.properties
@@ -1,25 +1,21 @@
 # Done to increase the memory available to gradle.
 org.gradle.jvmargs=-Xmx1G
 org.gradle.parallel=true
-
 # IntelliJ IDEA is not yet fully compatible with configuration cache, see: https://github.com/FabricMC/fabric-loom/issues/1349
 org.gradle.configuration-cache=false
-
 # Fabric Properties
 # check these on https://fabricmc.net/develop
-minecraft_version=1.21.11
-yarn_mappings=1.21.11+build.4
-loader_version=0.18.4
-loom_version=1.15-SNAPSHOT
-
+minecraft_version=26.1.2
+loader_version=0.19.3
+loom_version=1.17-SNAPSHOT
 # Mod Properties
 mod_version=2.4.1
 maven_group=blade.addon
 archives_base_name=blade-addons
-
 # Dependencies
-fabric_version=0.141.3+1.21.11
+fabric_api_version=0.152.1+26.1.2
 # I love dependencies sometimes breaking
-practical_config=ca2e4a5ea86f1f8ef9de3ae2bd3c1c1b6f6108ac
-modmenu_version =17.0.0-beta.2
-hypixel_api_version = 1.0.1
+practical_config=902a663c1b6c8917d02f12b7608eb373a9d3c1f3
+modmenu_version=18.0.0-beta.1
+hypixel_api_version=1.0.2
+dev_auth_version=1.2.2
diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties
index 23449a2..272ea02 100644
--- a/gradle/wrapper/gradle-wrapper.properties
+++ b/gradle/wrapper/gradle-wrapper.properties
@@ -1,7 +1,6 @@
+#Thu Jun 25 03:30:47 CEST 2026
 distributionBase=GRADLE_USER_HOME
 distributionPath=wrapper/dists
-distributionUrl=https\://services.gradle.org/distributions/gradle-9.2.1-bin.zip
-networkTimeout=10000
-validateDistributionUrl=true
+distributionUrl=https\://services.gradle.org/distributions/gradle-9.6.0-bin.zip
 zipStoreBase=GRADLE_USER_HOME
 zipStorePath=wrapper/dists
diff --git a/src/main/java/blade/addon/features/dungeon/BloodNotifier.java b/src/main/java/blade/addon/features/dungeon/BloodNotifier.java
index 902fed4..eeb9ed7 100644
--- a/src/main/java/blade/addon/features/dungeon/BloodNotifier.java
+++ b/src/main/java/blade/addon/features/dungeon/BloodNotifier.java
@@ -8,7 +8,7 @@ import blade.addon.utils.dungeon.DungeonClass;
 import blade.addon.utils.events.Events;
 import blade.addon.utils.rendering.RenderUtils;
 import config.practical.hud.HUDComponent;
-import net.minecraft.client.gui.GuiGraphics;
+import net.minecraft.client.gui.GuiGraphicsExtractor;
 import net.minecraft.network.chat.Component;
 import net.minecraft.sounds.SoundEvents;
 
@@ -53,7 +53,7 @@ public class BloodNotifier {
         return Dungeons.alertBloodSpawns && System.currentTimeMillis() - prevTime < 2000 && correctClass();
     }
 
-    public static void render(HUDComponent component, GuiGraphics graphics) {
+    public static void render(HUDComponent component, GuiGraphicsExtractor graphics) {
         RenderUtils.drawCenteredText(graphics, component, Component.literal("First four mobs spawned!"), Constants.RED);
     }
 
diff --git a/src/main/java/blade/addon/features/dungeon/ChestCounter.java b/src/main/java/blade/addon/features/dungeon/ChestCounter.java
index 3151e0e..13d0d31 100644
--- a/src/main/java/blade/addon/features/dungeon/ChestCounter.java
+++ b/src/main/java/blade/addon/features/dungeon/ChestCounter.java
@@ -9,13 +9,14 @@ import blade.addon.utils.dungeon.Phase;
 import blade.addon.utils.events.Events;
 import blade.addon.utils.rendering.RenderUtils;
 import config.practical.hud.HUDComponent;
-import java.util.regex.Matcher;
-import java.util.regex.Pattern;
 import net.minecraft.client.Minecraft;
-import net.minecraft.client.gui.GuiGraphics;
+import net.minecraft.client.gui.GuiGraphicsExtractor;
 import net.minecraft.network.chat.Component;
 import net.minecraft.sounds.SoundEvents;
 
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
 public class ChestCounter {
 
     private static final Pattern PATTERN = Pattern.compile("^ Unclaimed chests: (\\d+)$");
@@ -66,14 +67,14 @@ public class ChestCounter {
         return Dungeons.displayChestCount && Location.inDungeon();
     }
 
-    public static void render(HUDComponent component, GuiGraphics graphics) {
+    public static void render(HUDComponent component, GuiGraphicsExtractor graphics) {
         int x = component.getScaledX();
         int y = component.getScaledY();
 
         if (hasUpdatedData) {
             RenderUtils.drawPrefixedText(component, graphics, "Chests", Math.min(chestDisplayCount + countedChests, 60) + "");
         } else {
-            graphics.drawString(Minecraft.getInstance().font, Component.literal("go to the dungeon hub"), x, y, Constants.RED, true);
+            graphics.text(Minecraft.getInstance().font, Component.literal("go to the dungeon hub"), x, y, Constants.RED, true);
 
         }
     }
diff --git a/src/main/java/blade/addon/features/dungeon/ItemHighlight.java b/src/main/java/blade/addon/features/dungeon/ItemHighlight.java
index 47c6563..0cd9250 100644
--- a/src/main/java/blade/addon/features/dungeon/ItemHighlight.java
+++ b/src/main/java/blade/addon/features/dungeon/ItemHighlight.java
@@ -10,7 +10,7 @@ import com.mojang.blaze3d.vertex.PoseStack;
 import com.mojang.blaze3d.vertex.VertexConsumer;
 import net.fabricmc.fabric.api.client.event.lifecycle.v1.ClientEntityEvents;
 import net.fabricmc.fabric.api.client.event.lifecycle.v1.ClientTickEvents;
-import net.fabricmc.fabric.api.client.rendering.v1.world.WorldRenderContext;
+import net.fabricmc.fabric.api.client.rendering.v1.level.LevelRenderContext;
 import net.minecraft.client.Minecraft;
 import net.minecraft.client.multiplayer.ClientLevel;
 import net.minecraft.client.player.LocalPlayer;
@@ -57,7 +57,7 @@ public class ItemHighlight {
         RenderingEvents.FILLED_NO_DEPTH.register(ItemHighlight::render);
     }
 
-    private static void render(WorldRenderContext context, PoseStack matrixStack, VertexConsumer consumer) {
+    private static void render(LevelRenderContext context, PoseStack matrixStack, VertexConsumer consumer) {
         if (!Dungeons.highlightItems) return;
         double tickProgress = Minecraft.getInstance().getDeltaTracker().getGameTimeDeltaPartialTick(false);
 
diff --git a/src/main/java/blade/addon/features/dungeon/KeyNotifier.java b/src/main/java/blade/addon/features/dungeon/KeyNotifier.java
index 95b1eff..db47114 100644
--- a/src/main/java/blade/addon/features/dungeon/KeyNotifier.java
+++ b/src/main/java/blade/addon/features/dungeon/KeyNotifier.java
@@ -10,7 +10,7 @@ import blade.addon.utils.rendering.RenderUtils;
 import config.practical.hud.HUDComponent;
 import net.fabricmc.fabric.api.client.event.lifecycle.v1.ClientTickEvents;
 import net.minecraft.client.Minecraft;
-import net.minecraft.client.gui.GuiGraphics;
+import net.minecraft.client.gui.GuiGraphicsExtractor;
 import net.minecraft.core.component.DataComponents;
 import net.minecraft.network.chat.Component;
 import net.minecraft.sounds.SoundEvents;
@@ -19,6 +19,7 @@ import net.minecraft.world.entity.EquipmentSlot;
 import net.minecraft.world.entity.decoration.ArmorStand;
 import net.minecraft.world.item.ItemStack;
 import net.minecraft.world.item.component.ResolvableProfile;
+
 import java.util.concurrent.CopyOnWriteArrayList;
 import java.util.regex.Matcher;
 import java.util.regex.Pattern;
@@ -41,7 +42,7 @@ public class KeyNotifier {
     private static boolean isBloodKey = false;
 
     public static void init() {
-        ClientTickEvents.END_WORLD_TICK.register((world) -> {
+        ClientTickEvents.END_LEVEL_TICK.register((world) -> {
             if (!Location.inDungeon() || hasKey || !Dungeons.enableKeyNotifier || Phase.inBoss()) return;
 
             for (Entity entity : world.entitiesForRendering()) {
@@ -113,11 +114,11 @@ public class KeyNotifier {
         return Location.inDungeon() && hasKey && isValidClass() && !Phase.inBoss();
     }
 
-    public static void render(HUDComponent component, GuiGraphics context) {
+    public static void render(HUDComponent component, GuiGraphicsExtractor graphics) {
         int x = component.getScaledX();
         int y = component.getScaledY();
 
         Component text = isBloodKey ? BLOOD_KEY : WITHER_KEY;
-        RenderUtils.drawCenteredText(context, Minecraft.getInstance().font, text, x, y, component.getWidth(), 0xffffffff);
+        RenderUtils.drawCenteredText(graphics, Minecraft.getInstance().font, text, x, y, component.getWidth(), 0xffffffff);
     }
 }
diff --git a/src/main/java/blade/addon/features/dungeon/QuizTimer.java b/src/main/java/blade/addon/features/dungeon/QuizTimer.java
index 1377a9d..2f75aab 100644
--- a/src/main/java/blade/addon/features/dungeon/QuizTimer.java
+++ b/src/main/java/blade/addon/features/dungeon/QuizTimer.java
@@ -9,7 +9,7 @@ import blade.addon.utils.rendering.RenderUtils;
 import config.practical.hud.HUDComponent;
 import net.minecraft.client.Minecraft;
 import net.minecraft.client.gui.Font;
-import net.minecraft.client.gui.GuiGraphics;
+import net.minecraft.client.gui.GuiGraphicsExtractor;
 import net.minecraft.network.chat.Component;
 
 public class QuizTimer {
@@ -56,7 +56,7 @@ public class QuizTimer {
         return Dungeons.quizTimer && tick > 0;
     }
 
-    public static void render(HUDComponent component, GuiGraphics graphics) {
+    public static void render(HUDComponent component, GuiGraphicsExtractor graphics) {
         Font textRenderer = Minecraft.getInstance().font;
         if (textRenderer == null) return;
 
@@ -65,7 +65,7 @@ public class QuizTimer {
                     .append(Component.literal(stage + "/3").withColor(0xffffffff)
                             .append(Component.literal("): ").withColor(ExtraOptions.timerPrefixColor))
                             .append(Component.literal(Constants.DECIMAL_FORMAT.format(tick * Constants.TICK_DURATION) + "s").withColor(0xffffffff)));
-            graphics.drawString(textRenderer, drawnText, component.getScaledX(), component.getScaledY(), 0xffffffff, true);
+            graphics.text(textRenderer, drawnText, component.getScaledX(), component.getScaledY(), 0xffffffff, true);
         } else {
             RenderUtils.drawPrefixedTimer(component, graphics, "Quiz", tick);
         }
diff --git a/src/main/java/blade/addon/features/dungeon/RunStartValidator.java b/src/main/java/blade/addon/features/dungeon/RunStartValidator.java
index fbdd203..129271b 100644
--- a/src/main/java/blade/addon/features/dungeon/RunStartValidator.java
+++ b/src/main/java/blade/addon/features/dungeon/RunStartValidator.java
@@ -8,12 +8,8 @@ import blade.addon.utils.dungeon.Phase;
 import blade.addon.utils.events.Events;
 import blade.addon.utils.rendering.RenderUtils;
 import config.practical.hud.HUDComponent;
-import java.util.HashMap;
-import java.util.Map;
-import java.util.regex.Matcher;
-import java.util.regex.Pattern;
 import net.minecraft.client.Minecraft;
-import net.minecraft.client.gui.GuiGraphics;
+import net.minecraft.client.gui.GuiGraphicsExtractor;
 import net.minecraft.client.multiplayer.ClientLevel;
 import net.minecraft.client.player.LocalPlayer;
 import net.minecraft.network.chat.Component;
@@ -21,6 +17,11 @@ import net.minecraft.sounds.SoundEvents;
 import net.minecraft.world.scores.PlayerTeam;
 import net.minecraft.world.scores.Scoreboard;
 
+import java.util.HashMap;
+import java.util.Map;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
 public class RunStartValidator {
 
     private static final Pattern STARTING_PATTERN = Pattern.compile("^Starting in \\d second(s)?");
@@ -134,7 +135,7 @@ public class RunStartValidator {
         return Location.inDungeon() && !Phase.runStarted() && (hasDupeClasses || notEnoughPlayers);
     }
 
-    public static void render(HUDComponent component, GuiGraphics graphics) {
+    public static void render(HUDComponent component, GuiGraphicsExtractor graphics) {
         if (hasDupeClasses) {
             RenderUtils.drawCenteredText(graphics, component, DUPE_CLASS_TEXT);
         } else if (notEnoughPlayers) {
diff --git a/src/main/java/blade/addon/features/dungeon/SecretSpawnTimer.java b/src/main/java/blade/addon/features/dungeon/SecretSpawnTimer.java
index bc7b8b4..29e8fe1 100644
--- a/src/main/java/blade/addon/features/dungeon/SecretSpawnTimer.java
+++ b/src/main/java/blade/addon/features/dungeon/SecretSpawnTimer.java
@@ -7,12 +7,13 @@ import blade.addon.utils.dungeon.Phase;
 import blade.addon.utils.events.Events;
 import blade.addon.utils.rendering.RenderUtils;
 import config.practical.hud.HUDComponent;
-import java.util.regex.Matcher;
-import java.util.regex.Pattern;
 import net.minecraft.client.Minecraft;
-import net.minecraft.client.gui.GuiGraphics;
+import net.minecraft.client.gui.GuiGraphicsExtractor;
 import net.minecraft.network.chat.Component;
 
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
 public class SecretSpawnTimer {
 
     private static final Pattern PATTERN = Pattern.compile("^Time Elapsed: ");
@@ -41,7 +42,7 @@ public class SecretSpawnTimer {
         return Location.inDungeon() && Dungeons.enableSecretSpawnTimer && !Phase.inBoss() && Phase.runStarted();
     }
 
-    public static void render(HUDComponent component, GuiGraphics context) {
+    public static void render(HUDComponent component, GuiGraphicsExtractor context) {
         int x = component.getScaledX();
         int y = component.getScaledY();
 
diff --git a/src/main/java/blade/addon/features/dungeon/WarpCooldown.java b/src/main/java/blade/addon/features/dungeon/WarpCooldown.java
index e6b497d..657e8ea 100644
--- a/src/main/java/blade/addon/features/dungeon/WarpCooldown.java
+++ b/src/main/java/blade/addon/features/dungeon/WarpCooldown.java
@@ -4,9 +4,10 @@ import blade.addon.utils.config.values.Dungeons;
 import blade.addon.utils.events.Events;
 import blade.addon.utils.rendering.RenderUtils;
 import config.practical.hud.HUDComponent;
+import net.minecraft.client.gui.GuiGraphicsExtractor;
+
 import java.util.regex.Matcher;
 import java.util.regex.Pattern;
-import net.minecraft.client.gui.GuiGraphics;
 
 public class WarpCooldown {
 
@@ -42,7 +43,7 @@ public class WarpCooldown {
         }
     }
 
-    public static void render(HUDComponent component, GuiGraphics graphics) {
+    public static void render(HUDComponent component, GuiGraphicsExtractor graphics) {
         double timeRemaining = Math.max((endTime - System.currentTimeMillis()) / 1000.0, 0);
         RenderUtils.drawPrefixedTimer(component, graphics, "Warp Cooldown", timeRemaining);
     }
diff --git a/src/main/java/blade/addon/features/dungeon/f7/BossWaypoints.java b/src/main/java/blade/addon/features/dungeon/f7/BossWaypoints.java
index f5aa205..c721aab 100644
--- a/src/main/java/blade/addon/features/dungeon/f7/BossWaypoints.java
+++ b/src/main/java/blade/addon/features/dungeon/f7/BossWaypoints.java
@@ -15,7 +15,7 @@ import com.google.gson.JsonElement;
 import com.google.gson.JsonObject;
 import com.mojang.blaze3d.vertex.PoseStack;
 import com.mojang.blaze3d.vertex.VertexConsumer;
-import net.fabricmc.fabric.api.client.rendering.v1.world.WorldRenderContext;
+import net.fabricmc.fabric.api.client.rendering.v1.level.LevelRenderContext;
 import net.fabricmc.fabric.api.event.player.UseBlockCallback;
 import net.minecraft.core.BlockPos;
 import net.minecraft.gizmos.GizmoStyle;
@@ -141,7 +141,7 @@ public class BossWaypoints {
         return InteractionResult.PASS;
     }
 
-    private static void renderThroughWall(WorldRenderContext context, PoseStack matrixStack, VertexConsumer consumer) {
+    private static void renderThroughWall(LevelRenderContext context, PoseStack matrixStack, VertexConsumer consumer) {
         if (!isInValidArea()) return;
 
         waypoints.forEach(waypoint -> {
@@ -151,7 +151,7 @@ public class BossWaypoints {
         });
     }
 
-    private static void render(WorldRenderContext context, PoseStack matrixStack, VertexConsumer consumer) {
+    private static void render(LevelRenderContext context, PoseStack matrixStack, VertexConsumer consumer) {
         if (!isInValidArea()) return;
 
         waypoints.forEach(waypoint -> {
diff --git a/src/main/java/blade/addon/features/dungeon/f7/RelicTimer.java b/src/main/java/blade/addon/features/dungeon/f7/RelicTimer.java
index 3d47bad..c83094f 100644
--- a/src/main/java/blade/addon/features/dungeon/f7/RelicTimer.java
+++ b/src/main/java/blade/addon/features/dungeon/f7/RelicTimer.java
@@ -16,10 +16,10 @@ import com.mojang.blaze3d.vertex.PoseStack;
 import com.mojang.blaze3d.vertex.VertexConsumer;
 import config.practical.hud.HUDComponent;
 import net.fabricmc.fabric.api.client.event.lifecycle.v1.ClientTickEvents;
-import net.fabricmc.fabric.api.client.rendering.v1.world.WorldRenderContext;
+import net.fabricmc.fabric.api.client.rendering.v1.level.LevelRenderContext;
 import net.minecraft.ChatFormatting;
 import net.minecraft.client.Minecraft;
-import net.minecraft.client.gui.GuiGraphics;
+import net.minecraft.client.gui.GuiGraphicsExtractor;
 import net.minecraft.core.BlockPos;
 import net.minecraft.gizmos.GizmoStyle;
 import net.minecraft.gizmos.Gizmos;
@@ -31,6 +31,7 @@ import net.minecraft.world.entity.decoration.ArmorStand;
 import net.minecraft.world.item.ItemStack;
 import net.minecraft.world.phys.AABB;
 import net.minecraft.world.phys.BlockHitResult;
+
 import java.util.regex.Matcher;
 import java.util.regex.Pattern;
 
@@ -122,7 +123,7 @@ public class RelicTimer {
             return false;
         });
 
-        ClientTickEvents.END_WORLD_TICK.register(world -> {
+        ClientTickEvents.END_LEVEL_TICK.register(world -> {
             if (!Location.inDungeon() || !Phase.inP5() || !Floor7.showAllRelicTimes || sentRelicTimes) return;
 
             Iterable<Entity> entities = world.entitiesForRendering();
@@ -259,7 +260,7 @@ public class RelicTimer {
         Misc.addChatMessage(Component.literal("Relic: " + pickedupRelic));
     }
 
-    private static void worldRender(WorldRenderContext context, PoseStack matrixStack, VertexConsumer consumer) {
+    private static void worldRender(LevelRenderContext context, PoseStack matrixStack, VertexConsumer consumer) {
         if (pickedupRelic == null || !Floor7.renderRelicHighlight) return;
 
         AABB box = pickedupRelic.box;
@@ -271,7 +272,7 @@ public class RelicTimer {
         return Floor7.enableRelicStartTimer && Location.inDungeon() && Phase.inP5() && tick > -1 && !Floor7.replaceWithProgressBar;
     }
 
-    public static void render(HUDComponent component, GuiGraphics graphics) {
+    public static void render(HUDComponent component, GuiGraphicsExtractor graphics) {
         int color = tick > 7 ? GREEN_COLOR : RED_COLOR;
         RenderUtils.drawTimer(component, graphics, tick, color);
     }
@@ -281,7 +282,7 @@ public class RelicTimer {
         return Floor7.enableRelicStartTimer && Location.inDungeon() && Phase.inP5() && tick > -1 && Floor7.replaceWithProgressBar;
     }
 
-    public static void renderProgressBar(HUDComponent component, GuiGraphics graphics) {
+    public static void renderProgressBar(HUDComponent component, GuiGraphicsExtractor graphics) {
         int x = component.getScaledX();
         int y = component.getScaledY();
 
diff --git a/src/main/java/blade/addon/features/dungeon/f7/dragons/DragSpawnTimer.java b/src/main/java/blade/addon/features/dungeon/f7/dragons/DragSpawnTimer.java
index 594acd2..fa7f96f 100644
--- a/src/main/java/blade/addon/features/dungeon/f7/dragons/DragSpawnTimer.java
+++ b/src/main/java/blade/addon/features/dungeon/f7/dragons/DragSpawnTimer.java
@@ -6,7 +6,7 @@ import blade.addon.utils.debug.Debug;
 import blade.addon.utils.events.Events;
 import blade.addon.utils.rendering.RenderUtils;
 import config.practical.hud.HUDComponent;
-import net.minecraft.client.gui.GuiGraphics;
+import net.minecraft.client.gui.GuiGraphicsExtractor;
 import net.minecraft.core.particles.ParticleTypes;
 import net.minecraft.network.chat.Component;
 import net.minecraft.network.protocol.game.ClientboundLevelParticlesPacket;
@@ -99,7 +99,7 @@ public class DragSpawnTimer {
         return Floor7.dragSpawnTimers && currentDragon != Dragon.NONE;
     }
 
-    public static void render(HUDComponent component, GuiGraphics graphics) {
+    public static void render(HUDComponent component, GuiGraphicsExtractor graphics) {
         RenderUtils.drawTimer(component, graphics, tick, currentDragon.color);
     }
 }
diff --git a/src/main/java/blade/addon/features/dungeon/f7/dragons/DragonHealth.java b/src/main/java/blade/addon/features/dungeon/f7/dragons/DragonHealth.java
index dfecef1..32c99f6 100644
--- a/src/main/java/blade/addon/features/dungeon/f7/dragons/DragonHealth.java
+++ b/src/main/java/blade/addon/features/dungeon/f7/dragons/DragonHealth.java
@@ -10,10 +10,11 @@ import blade.addon.utils.rendering.RenderingEvents;
 import com.mojang.blaze3d.vertex.PoseStack;
 import com.mojang.blaze3d.vertex.VertexConsumer;
 import net.fabricmc.fabric.api.client.event.lifecycle.v1.ClientTickEvents;
-import net.fabricmc.fabric.api.client.rendering.v1.world.WorldRenderContext;
+import net.fabricmc.fabric.api.client.rendering.v1.level.LevelRenderContext;
 import net.minecraft.network.chat.Component;
 import net.minecraft.world.entity.boss.enderdragon.EnderDragon;
 import net.minecraft.world.phys.Vec3;
+
 import java.util.concurrent.ConcurrentLinkedQueue;
 
 public class DragonHealth {
@@ -42,7 +43,7 @@ public class DragonHealth {
             return false;
         });
 
-        ClientTickEvents.END_WORLD_TICK.register(world -> dragons.removeIf(dataHolder -> dataHolder.dragon.isRemoved()));
+        ClientTickEvents.END_LEVEL_TICK.register(world -> dragons.removeIf(dataHolder -> dataHolder.dragon.isRemoved()));
         RenderingEvents.FILLED.register(DragonHealth::render);
         Events.ON_LOCATION_CHANGE.register(newLocation -> {
             reset();
@@ -61,7 +62,7 @@ public class DragonHealth {
         return Constants.RED;
     }
 
-    private static void render(WorldRenderContext context, PoseStack matrixStack, VertexConsumer consumer) {
+    private static void render(LevelRenderContext context, PoseStack matrixStack, VertexConsumer consumer) {
         if (!Floor7.dragonHealth) return;
 
         dragons.forEach(dataHolder -> {
diff --git a/src/main/java/blade/addon/features/dungeon/f7/dragons/DragonTracer.java b/src/main/java/blade/addon/features/dungeon/f7/dragons/DragonTracer.java
index d057daf..f5a7155 100644
--- a/src/main/java/blade/addon/features/dungeon/f7/dragons/DragonTracer.java
+++ b/src/main/java/blade/addon/features/dungeon/f7/dragons/DragonTracer.java
@@ -5,7 +5,7 @@ import blade.addon.utils.rendering.RenderUtils;
 import blade.addon.utils.rendering.RenderingEvents;
 import com.mojang.blaze3d.vertex.PoseStack;
 import com.mojang.blaze3d.vertex.VertexConsumer;
-import net.fabricmc.fabric.api.client.rendering.v1.world.WorldRenderContext;
+import net.fabricmc.fabric.api.client.rendering.v1.level.LevelRenderContext;
 
 public class DragonTracer {
 
@@ -13,7 +13,7 @@ public class DragonTracer {
         RenderingEvents.LINE.register(DragonTracer::render);
     }
 
-    private static void render(WorldRenderContext context, PoseStack matrixStack, VertexConsumer consumer) {
+    private static void render(LevelRenderContext context, PoseStack matrixStack, VertexConsumer consumer) {
         if (!Floor7.dragonTracer) return;
 
         Dragon dragon = DragSpawnTimer.currentDragon;
diff --git a/src/main/java/blade/addon/features/dungeon/f7/invincibility/InvincibilityDuration.java b/src/main/java/blade/addon/features/dungeon/f7/invincibility/InvincibilityDuration.java
index 0634731..c5b792a 100644
--- a/src/main/java/blade/addon/features/dungeon/f7/invincibility/InvincibilityDuration.java
+++ b/src/main/java/blade/addon/features/dungeon/f7/invincibility/InvincibilityDuration.java
@@ -4,7 +4,7 @@ import blade.addon.utils.config.values.Dungeons;
 import blade.addon.utils.events.Events;
 import blade.addon.utils.rendering.RenderUtils;
 import config.practical.hud.HUDComponent;
-import net.minecraft.client.gui.GuiGraphics;
+import net.minecraft.client.gui.GuiGraphicsExtractor;
 
 public class InvincibilityDuration {
 
@@ -26,7 +26,7 @@ public class InvincibilityDuration {
         return  ticks > 0 && Dungeons.InvincibilityDuration;
     }
 
-    public static void render(HUDComponent component, GuiGraphics graphics) {
+    public static void render(HUDComponent component, GuiGraphicsExtractor graphics) {
         int color = 0xffffffff;
         if (Dungeons.useStatusColorForInvincibility) {
             color = RenderUtils.getStatusColor(40, 20, ticks);
diff --git a/src/main/java/blade/addon/features/dungeon/f7/invincibility/InvincibilityTimer.java b/src/main/java/blade/addon/features/dungeon/f7/invincibility/InvincibilityTimer.java
index 3b24c1a..d7b1656 100644
--- a/src/main/java/blade/addon/features/dungeon/f7/invincibility/InvincibilityTimer.java
+++ b/src/main/java/blade/addon/features/dungeon/f7/invincibility/InvincibilityTimer.java
@@ -8,15 +8,16 @@ import blade.addon.utils.config.values.Dungeons;
 import blade.addon.utils.dungeon.Phase;
 import blade.addon.utils.events.Events;
 import config.practical.hud.HUDComponent;
-import java.util.regex.Matcher;
-import java.util.regex.Pattern;
 import net.minecraft.client.Minecraft;
-import net.minecraft.client.gui.GuiGraphics;
+import net.minecraft.client.gui.GuiGraphicsExtractor;
 import net.minecraft.client.renderer.RenderPipelines;
 import net.minecraft.network.chat.Component;
 import net.minecraft.resources.Identifier;
 import net.minecraft.world.item.ItemStack;
 
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
 public class InvincibilityTimer {
 
     private static final int TEXT_HEIGHT = 9;
@@ -152,12 +153,12 @@ public class InvincibilityTimer {
         return (double) spiritMaskTicks / SPIRIT_MASK_COOLDOWN;
     }
 
-    private static void drawSprite(GuiGraphics context, Identifier identifier, int x, int y, boolean isOn, int ticks, Component timerText) {
+    private static void drawSprite(GuiGraphicsExtractor context, Identifier identifier, int x, int y, boolean isOn, int ticks, Component timerText) {
         int color = (ticks > 0 ? Constants.RED : isOn ? Constants.YELLOW : Constants.GREEN);
         context.fill(x, y, x + SPRITE_SIZE, y + SPRITE_SIZE, color);
         context.blitSprite(RenderPipelines.GUI_TEXTURED, identifier, x, y, SPRITE_SIZE, SPRITE_SIZE, 0xffffffff);
         if (ticks > 0) {
-            context.drawString(Minecraft.getInstance().font, timerText, x + TEXT_HEIGHT * 2, y, 0xffffffff, true);
+            context.text(Minecraft.getInstance().font, timerText, x + TEXT_HEIGHT * 2, y, 0xffffffff, true);
         }
 
     }
@@ -190,7 +191,7 @@ public class InvincibilityTimer {
         }
     }
 
-    public static void render(HUDComponent component, GuiGraphics graphics) {
+    public static void render(HUDComponent component, GuiGraphicsExtractor graphics) {
         int x = component.getScaledX();
         int y = component.getScaledY();
 
@@ -200,13 +201,13 @@ public class InvincibilityTimer {
             drawSprite(graphics, PHOENIX_SPRITE, x, y + (SPRITE_SIZE + 1) * 2, phoenixOn, phoenixTicks, formatTimer(phoenixTicks));
         } else {
             if (Dungeons.removeMaskPart) {
-                graphics.drawString(Minecraft.getInstance().font, getText(bonzoMaskTicks, bonzoMaskOn, "Bonzo "), x, y, 0xffffffff, true);
-                graphics.drawString(Minecraft.getInstance().font, getText(spiritMaskTicks, spiritMaskOn, "Spirit "), x, y + TEXT_HEIGHT, 0xffffffff, true);
+                graphics.text(Minecraft.getInstance().font, getText(bonzoMaskTicks, bonzoMaskOn, "Bonzo "), x, y, 0xffffffff, true);
+                graphics.text(Minecraft.getInstance().font, getText(spiritMaskTicks, spiritMaskOn, "Spirit "), x, y + TEXT_HEIGHT, 0xffffffff, true);
             } else {
-                graphics.drawString(Minecraft.getInstance().font, getText(bonzoMaskTicks, bonzoMaskOn, "Bonzo's Mask "), x, y, 0xffffffff, true);
-                graphics.drawString(Minecraft.getInstance().font, getText(spiritMaskTicks, spiritMaskOn, "Spirit Mask "), x, y + TEXT_HEIGHT, 0xffffffff, true);
+                graphics.text(Minecraft.getInstance().font, getText(bonzoMaskTicks, bonzoMaskOn, "Bonzo's Mask "), x, y, 0xffffffff, true);
+                graphics.text(Minecraft.getInstance().font, getText(spiritMaskTicks, spiritMaskOn, "Spirit Mask "), x, y + TEXT_HEIGHT, 0xffffffff, true);
             }
-            graphics.drawString(Minecraft.getInstance().font, getText(phoenixTicks, phoenixOn, "Phoenix "), x, y + TEXT_HEIGHT * 2, 0xffffffff, true);
+            graphics.text(Minecraft.getInstance().font, getText(phoenixTicks, phoenixOn, "Phoenix "), x, y + TEXT_HEIGHT * 2, 0xffffffff, true);
         }
     }
 }
diff --git a/src/main/java/blade/addon/features/dungeon/f7/invincibility/MaskHighlight.java b/src/main/java/blade/addon/features/dungeon/f7/invincibility/MaskHighlight.java
index eb03afb..36bea37 100644
--- a/src/main/java/blade/addon/features/dungeon/f7/invincibility/MaskHighlight.java
+++ b/src/main/java/blade/addon/features/dungeon/f7/invincibility/MaskHighlight.java
@@ -3,7 +3,7 @@ package blade.addon.features.dungeon.f7.invincibility;
 import blade.addon.utils.config.values.Dungeons;
 import blade.addon.utils.data.ItemUtil;
 import blade.addon.utils.rendering.DrawEvents;
-import net.minecraft.client.gui.GuiGraphics;
+import net.minecraft.client.gui.GuiGraphicsExtractor;
 import net.minecraft.world.item.ItemStack;
 
 public class MaskHighlight {
@@ -14,7 +14,7 @@ public class MaskHighlight {
         DrawEvents.INVENTORY_SLOT_AFTER.register(MaskHighlight::draw);
     }
 
-    private static void draw(GuiGraphics context, ItemStack stack, int x, int y) {
+    private static void draw(GuiGraphicsExtractor context, ItemStack stack, int x, int y) {
         if (!Dungeons.maskHighlight) return;
 
         MaskHolder holder = (MaskHolder) (Object) stack;
diff --git a/src/main/java/blade/addon/features/dungeon/f7/location/LocationNotifier.java b/src/main/java/blade/addon/features/dungeon/f7/location/LocationNotifier.java
index b52faa8..7aa3fc8 100644
--- a/src/main/java/blade/addon/features/dungeon/f7/location/LocationNotifier.java
+++ b/src/main/java/blade/addon/features/dungeon/f7/location/LocationNotifier.java
@@ -11,9 +11,10 @@ import blade.addon.utils.events.Events;
 import blade.addon.utils.rendering.RenderUtils;
 import config.practical.hud.HUDComponent;
 import net.fabricmc.fabric.api.client.event.lifecycle.v1.ClientTickEvents;
-import net.minecraft.client.gui.GuiGraphics;
+import net.minecraft.client.gui.GuiGraphicsExtractor;
 import net.minecraft.network.chat.Component;
 import net.minecraft.network.chat.Style;
+
 import java.util.regex.Matcher;
 import java.util.regex.Pattern;
 
@@ -65,7 +66,7 @@ public class LocationNotifier {
         return ticks > 0 && Floor7.displayLocationNotification;
     }
 
-    public static void render(HUDComponent component, GuiGraphics graphics) {
+    public static void render(HUDComponent component, GuiGraphicsExtractor graphics) {
         RenderUtils.drawCenteredText(graphics, component, notification);
     }
 }
diff --git a/src/main/java/blade/addon/features/dungeon/f7/location/PositionMessages.java b/src/main/java/blade/addon/features/dungeon/f7/location/PositionMessages.java
index fcc6af0..a4bcc63 100644
--- a/src/main/java/blade/addon/features/dungeon/f7/location/PositionMessages.java
+++ b/src/main/java/blade/addon/features/dungeon/f7/location/PositionMessages.java
@@ -9,13 +9,14 @@ import blade.addon.utils.rendering.RenderingEvents;
 import com.mojang.blaze3d.vertex.PoseStack;
 import com.mojang.blaze3d.vertex.VertexConsumer;
 import net.fabricmc.fabric.api.client.event.lifecycle.v1.ClientTickEvents;
-import net.fabricmc.fabric.api.client.rendering.v1.world.WorldRenderContext;
+import net.fabricmc.fabric.api.client.rendering.v1.level.LevelRenderContext;
 import net.minecraft.client.Minecraft;
 import net.minecraft.client.player.LocalPlayer;
 import net.minecraft.gizmos.GizmoStyle;
 import net.minecraft.gizmos.Gizmos;
 import net.minecraft.util.ARGB;
 import net.minecraft.world.phys.AABB;
+
 import java.util.ArrayList;
 
 public class PositionMessages {
@@ -97,7 +98,7 @@ public class PositionMessages {
         }
     }
 
-    private static void render(WorldRenderContext context, PoseStack matrixStack, VertexConsumer consumer) {
+    private static void render(LevelRenderContext context, PoseStack matrixStack, VertexConsumer consumer) {
         if (!Location.inDungeon() || !Debug.renderPositions) return;
 
         for (PositionMessage positionMessage : positionMessages) {
diff --git a/src/main/java/blade/addon/features/dungeon/f7/maxor/CrystalSpawn.java b/src/main/java/blade/addon/features/dungeon/f7/maxor/CrystalSpawn.java
index 980fbe3..5a2a892 100644
--- a/src/main/java/blade/addon/features/dungeon/f7/maxor/CrystalSpawn.java
+++ b/src/main/java/blade/addon/features/dungeon/f7/maxor/CrystalSpawn.java
@@ -10,14 +10,15 @@ import blade.addon.utils.events.Events;
 import blade.addon.utils.rendering.RenderUtils;
 import blade.addon.utils.times.PersonalBests;
 import config.practical.hud.HUDComponent;
-import java.util.regex.Matcher;
-import java.util.regex.Pattern;
 import net.minecraft.client.Minecraft;
-import net.minecraft.client.gui.GuiGraphics;
+import net.minecraft.client.gui.GuiGraphicsExtractor;
 import net.minecraft.client.player.LocalPlayer;
 import net.minecraft.network.chat.Component;
 import net.minecraft.world.entity.boss.enderdragon.EndCrystal;
 
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
 public class CrystalSpawn {
 
     private static final Pattern RELIC_PICK_UP = Pattern.compile("(\\w+) picked up an Energy Crystal!$");
@@ -98,7 +99,7 @@ public class CrystalSpawn {
         return tick > 0 && Location.inDungeon() && Phase.inP1() && Floor7.enableCrystalSpawnTime;
     }
 
-    public static void render(HUDComponent component, GuiGraphics graphics) {
+    public static void render(HUDComponent component, GuiGraphicsExtractor graphics) {
         RenderUtils.drawTimer(component, graphics, tick, Constants.LIGHT_PURPLE);
     }
 
@@ -106,7 +107,7 @@ public class CrystalSpawn {
         return (Floor7.instantlyDisplayCrystalReminder || tickSincePicked > REMINDER_TICK) && Location.inDungeon() && Phase.inP1() && Floor7.crystalPlaceReminder && pickedUp;
     }
 
-    public static void renderNotification(HUDComponent component, GuiGraphics graphics) {
+    public static void renderNotification(HUDComponent component, GuiGraphicsExtractor graphics) {
         RenderUtils.drawCenteredText(graphics, component, Component.literal("§bPlace Crystal!"));
     }
 
diff --git a/src/main/java/blade/addon/features/dungeon/f7/maxor/MaxorStun.java b/src/main/java/blade/addon/features/dungeon/f7/maxor/MaxorStun.java
index 8b60e3d..2836163 100644
--- a/src/main/java/blade/addon/features/dungeon/f7/maxor/MaxorStun.java
+++ b/src/main/java/blade/addon/features/dungeon/f7/maxor/MaxorStun.java
@@ -7,7 +7,7 @@ import blade.addon.utils.dungeon.Phase;
 import blade.addon.utils.events.Events;
 import blade.addon.utils.rendering.RenderUtils;
 import config.practical.hud.HUDComponent;
-import net.minecraft.client.gui.GuiGraphics;
+import net.minecraft.client.gui.GuiGraphicsExtractor;
 
 public class MaxorStun {
 
@@ -51,7 +51,7 @@ public class MaxorStun {
         return tick > 0 && Location.inDungeon() && Phase.inP1() && Floor7.maxorStunDuration && stunned;
     }
 
-    public static void render(HUDComponent component, GuiGraphics graphics) {
+    public static void render(HUDComponent component, GuiGraphicsExtractor graphics) {
         RenderUtils.drawPrefixedText(component, graphics, "Stunned", TextUtil.formatTicks(tick));
     }
 
diff --git a/src/main/java/blade/addon/features/dungeon/f7/maxor/MaxorTickTimer.java b/src/main/java/blade/addon/features/dungeon/f7/maxor/MaxorTickTimer.java
index eebfec1..7f296ee 100644
--- a/src/main/java/blade/addon/features/dungeon/f7/maxor/MaxorTickTimer.java
+++ b/src/main/java/blade/addon/features/dungeon/f7/maxor/MaxorTickTimer.java
@@ -6,7 +6,7 @@ import blade.addon.utils.dungeon.Phase;
 import blade.addon.utils.events.Events;
 import blade.addon.utils.rendering.RenderUtils;
 import config.practical.hud.HUDComponent;
-import net.minecraft.client.gui.GuiGraphics;
+import net.minecraft.client.gui.GuiGraphicsExtractor;
 
 public class MaxorTickTimer {
 
@@ -28,7 +28,7 @@ public class MaxorTickTimer {
         return Floor7.enableMaxorTickTimer && Location.inDungeon() && Phase.inP1();
     }
 
-    public static void render(HUDComponent component, GuiGraphics graphics) {
+    public static void render(HUDComponent component, GuiGraphicsExtractor graphics) {
         RenderUtils.drawTimer(component, graphics, tick, 0xffffffff);
     }
 
diff --git a/src/main/java/blade/addon/features/dungeon/f7/storm/DistanceToLedge.java b/src/main/java/blade/addon/features/dungeon/f7/storm/DistanceToLedge.java
index 0026c1a..ff74deb 100644
--- a/src/main/java/blade/addon/features/dungeon/f7/storm/DistanceToLedge.java
+++ b/src/main/java/blade/addon/features/dungeon/f7/storm/DistanceToLedge.java
@@ -7,7 +7,7 @@ import blade.addon.utils.dungeon.Phase;
 import blade.addon.utils.rendering.RenderUtils;
 import config.practical.hud.HUDComponent;
 import net.minecraft.client.Minecraft;
-import net.minecraft.client.gui.GuiGraphics;
+import net.minecraft.client.gui.GuiGraphicsExtractor;
 import net.minecraft.client.player.LocalPlayer;
 import net.minecraft.world.phys.AABB;
 
@@ -39,7 +39,7 @@ public class DistanceToLedge {
         return Phase.inP2() && !Phase.stormDead() && (DungeonClass.isClass(DungeonClass.MAGE) || DungeonClass.isClass(DungeonClass.ARCHER));
     }
 
-    public static void render(HUDComponent component, GuiGraphics graphics) {
+    public static void render(HUDComponent component, GuiGraphicsExtractor graphics) {
         RenderUtils.drawTimer(component, graphics, getDistance(), 0xffffffff);
     }
 }
diff --git a/src/main/java/blade/addon/features/dungeon/f7/storm/PillarExplode.java b/src/main/java/blade/addon/features/dungeon/f7/storm/PillarExplode.java
index 4aa47c1..814a2bb 100644
--- a/src/main/java/blade/addon/features/dungeon/f7/storm/PillarExplode.java
+++ b/src/main/java/blade/addon/features/dungeon/f7/storm/PillarExplode.java
@@ -8,7 +8,7 @@ import blade.addon.utils.dungeon.Phase;
 import blade.addon.utils.events.Events;
 import blade.addon.utils.rendering.RenderUtils;
 import config.practical.hud.HUDComponent;
-import net.minecraft.client.gui.GuiGraphics;
+import net.minecraft.client.gui.GuiGraphicsExtractor;
 import net.minecraft.network.chat.Component;
 import net.minecraft.sounds.SoundEvents;
 
@@ -43,7 +43,7 @@ public class PillarExplode {
         return Floor7.timePillarExplosion && tick > 0;
     }
 
-    public static void renderTimer(HUDComponent component, GuiGraphics graphics) {
+    public static void renderTimer(HUDComponent component, GuiGraphicsExtractor graphics) {
         int color = tick < 6 ? Constants.GREEN : Constants.RED;
         RenderUtils.drawTimer(component, graphics, tick, color);
     }
@@ -52,7 +52,7 @@ public class PillarExplode {
         return Floor7.notifyStormCrush && tick > 0;
     }
 
-    public static void render(HUDComponent component, GuiGraphics graphics) {
+    public static void render(HUDComponent component, GuiGraphicsExtractor graphics) {
         RenderUtils.drawCenteredText(graphics, component, Component.literal("§6||| §bStorm crushed! §6|||"));
     }
 }
diff --git a/src/main/java/blade/addon/features/dungeon/f7/storm/StormTickTimer.java b/src/main/java/blade/addon/features/dungeon/f7/storm/StormTickTimer.java
index 963a4d0..ed9103e 100644
--- a/src/main/java/blade/addon/features/dungeon/f7/storm/StormTickTimer.java
+++ b/src/main/java/blade/addon/features/dungeon/f7/storm/StormTickTimer.java
@@ -10,10 +10,11 @@ import blade.addon.utils.dungeon.Phase;
 import blade.addon.utils.events.Events;
 import blade.addon.utils.rendering.RenderUtils;
 import config.practical.hud.HUDComponent;
+import net.minecraft.client.gui.GuiGraphicsExtractor;
+import net.minecraft.network.chat.Component;
+
 import java.util.regex.Matcher;
 import java.util.regex.Pattern;
-import net.minecraft.client.gui.GuiGraphics;
-import net.minecraft.network.chat.Component;
 
 public class StormTickTimer {
 
@@ -72,7 +73,7 @@ public class StormTickTimer {
         return Floor7.enableStormTickTimer && Location.inDungeon() && Phase.inP2() && !Phase.stormDead();
     }
 
-    public static void render(HUDComponent component, GuiGraphics graphics) {
+    public static void render(HUDComponent component, GuiGraphicsExtractor graphics) {
         double num = tick * Constants.TICK_DURATION;
         if (Floor7.tickDownStormTickTimer) {
             num = CRUSH_TICK * Constants.TICK_DURATION - num;
@@ -84,7 +85,7 @@ public class StormTickTimer {
         return Floor7.enableStormDeathTime && Location.inDungeon() && Phase.inP2() && !Phase.stormDead() && deathTime > 0 && deathStartDisplayTime > System.currentTimeMillis() - DEATH_DISPLAY_DURATION;
     }
 
-    public static void renderDeathTime(HUDComponent component, GuiGraphics graphics) {
+    public static void renderDeathTime(HUDComponent component, GuiGraphicsExtractor graphics) {
         RenderUtils.drawTimer(component, graphics, deathTime, Constants.DARK_PURPLE);
     }
 }
diff --git a/src/main/java/blade/addon/features/dungeon/f7/terms/CurrentSection.java b/src/main/java/blade/addon/features/dungeon/f7/terms/CurrentSection.java
index 0f5c86d..ecfc77f 100644
--- a/src/main/java/blade/addon/features/dungeon/f7/terms/CurrentSection.java
+++ b/src/main/java/blade/addon/features/dungeon/f7/terms/CurrentSection.java
@@ -5,7 +5,7 @@ import blade.addon.utils.dungeon.Phase;
 import blade.addon.utils.events.Events;
 import blade.addon.utils.rendering.RenderUtils;
 import config.practical.hud.HUDComponent;
-import net.minecraft.client.gui.GuiGraphics;
+import net.minecraft.client.gui.GuiGraphicsExtractor;
 
 public class CurrentSection {
 
@@ -26,7 +26,7 @@ public class CurrentSection {
         return Floor7.showCurrentSection && Phase.inTerminals();
     }
 
-    public static void render(HUDComponent component, GuiGraphics graphics) {
+    public static void render(HUDComponent component, GuiGraphicsExtractor graphics) {
         RenderUtils.drawPrefixedText(component, graphics, "Section", " " + section);
     }
 
diff --git a/src/main/java/blade/addon/features/dungeon/f7/terms/DeviceNotifier.java b/src/main/java/blade/addon/features/dungeon/f7/terms/DeviceNotifier.java
index bf14599..7c08662 100644
--- a/src/main/java/blade/addon/features/dungeon/f7/terms/DeviceNotifier.java
+++ b/src/main/java/blade/addon/features/dungeon/f7/terms/DeviceNotifier.java
@@ -8,7 +8,7 @@ import blade.addon.utils.events.Events;
 import blade.addon.utils.rendering.RenderUtils;
 import config.practical.hud.HUDComponent;
 import net.minecraft.client.Minecraft;
-import net.minecraft.client.gui.GuiGraphics;
+import net.minecraft.client.gui.GuiGraphicsExtractor;
 import net.minecraft.client.player.LocalPlayer;
 import net.minecraft.network.chat.Component;
 import net.minecraft.sounds.SoundEvents;
@@ -74,7 +74,7 @@ public class DeviceNotifier {
         return showNotification;
     }
 
-    public static void render(HUDComponent component, GuiGraphics graphics) {
+    public static void render(HUDComponent component, GuiGraphicsExtractor graphics) {
         if (System.currentTimeMillis() - completedTime >= TOTAL_DURATION) showNotification = false;
         RenderUtils.drawCenteredText(graphics, component, Component.literal("§aDevice Completed!"));
     }
diff --git a/src/main/java/blade/addon/features/dungeon/f7/terms/GoldorTickTimer.java b/src/main/java/blade/addon/features/dungeon/f7/terms/GoldorTickTimer.java
index 2daa728..90fc5c2 100644
--- a/src/main/java/blade/addon/features/dungeon/f7/terms/GoldorTickTimer.java
+++ b/src/main/java/blade/addon/features/dungeon/f7/terms/GoldorTickTimer.java
@@ -7,7 +7,7 @@ import blade.addon.utils.dungeon.Phase;
 import blade.addon.utils.events.Events;
 import blade.addon.utils.rendering.RenderUtils;
 import config.practical.hud.HUDComponent;
-import net.minecraft.client.gui.GuiGraphics;
+import net.minecraft.client.gui.GuiGraphicsExtractor;
 
 public class GoldorTickTimer {
 
@@ -31,7 +31,7 @@ public class GoldorTickTimer {
         return Floor7.enableGoldorTickTimer && Location.inDungeon() && Phase.inTerminals();
     }
 
-    public static void render(HUDComponent component, GuiGraphics graphics) {
+    public static void render(HUDComponent component, GuiGraphicsExtractor graphics) {
         double num = tick * Constants.TICK_DURATION;
         double mod = num % 3;
         if (Floor7.inDeathTicks && !Floor7.makeGoldorTickUp) mod = 3.0 - mod;
diff --git a/src/main/java/blade/addon/features/dungeon/f7/terms/LeapNotification.java b/src/main/java/blade/addon/features/dungeon/f7/terms/LeapNotification.java
index 754840a..6c8e5b8 100644
--- a/src/main/java/blade/addon/features/dungeon/f7/terms/LeapNotification.java
+++ b/src/main/java/blade/addon/features/dungeon/f7/terms/LeapNotification.java
@@ -7,13 +7,14 @@ import blade.addon.utils.dungeon.Phase;
 import blade.addon.utils.rendering.RenderUtils;
 import config.practical.hud.HUDComponent;
 import net.fabricmc.fabric.api.client.event.lifecycle.v1.ClientTickEvents;
-import net.minecraft.client.gui.GuiGraphics;
+import net.minecraft.client.gui.GuiGraphicsExtractor;
 import net.minecraft.client.multiplayer.ClientLevel;
 import net.minecraft.client.player.LocalPlayer;
 import net.minecraft.network.chat.Component;
 import net.minecraft.world.entity.Entity;
 import net.minecraft.world.phys.AABB;
 import net.minecraft.world.phys.Vec3;
+
 import java.util.List;
 
 /**
@@ -100,7 +101,7 @@ public class LeapNotification {
         return Floor7.leapNotifications && inBounds && Phase.inBoss() && Location.inDungeon();
     }
 
-    public static void render(HUDComponent component, GuiGraphics graphics) {
+    public static void render(HUDComponent component, GuiGraphicsExtractor graphics) {
         int maxCount = getMaxCount();
 
         String startFormat;
diff --git a/src/main/java/blade/addon/features/dungeon/f7/terms/MelodyWarning.java b/src/main/java/blade/addon/features/dungeon/f7/terms/MelodyWarning.java
index 0e7d439..c83ae12 100644
--- a/src/main/java/blade/addon/features/dungeon/f7/terms/MelodyWarning.java
+++ b/src/main/java/blade/addon/features/dungeon/f7/terms/MelodyWarning.java
@@ -10,14 +10,15 @@ import blade.addon.utils.dungeon.Phase;
 import blade.addon.utils.events.Events;
 import blade.addon.utils.rendering.RenderUtils;
 import config.practical.hud.HUDComponent;
-import java.util.concurrent.CopyOnWriteArrayList;
-import java.util.regex.Matcher;
-import java.util.regex.Pattern;
-import net.minecraft.client.gui.GuiGraphics;
+import net.minecraft.client.gui.GuiGraphicsExtractor;
 import net.minecraft.network.chat.Component;
 import net.minecraft.network.chat.MutableComponent;
 import net.minecraft.network.chat.Style;
 
+import java.util.concurrent.CopyOnWriteArrayList;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
 public class MelodyWarning {
 
     private static final Pattern PATTERN = Pattern.compile("(\\d+)%");
@@ -80,7 +81,7 @@ public class MelodyWarning {
         return melodyStarted && Floor7.notifiyMelody && !ownUsername;
     }
 
-    public static void render(HUDComponent component, GuiGraphics graphics) {
+    public static void render(HUDComponent component, GuiGraphicsExtractor graphics) {
         DungeonClass dungeonClass = DungeonClass.getClass(name);
         int num = Math.min(furthestProgress / 25, 3);
 
diff --git a/src/main/java/blade/addon/features/dungeon/f7/terms/SectionCompletion.java b/src/main/java/blade/addon/features/dungeon/f7/terms/SectionCompletion.java
index 9ee7509..b268232 100644
--- a/src/main/java/blade/addon/features/dungeon/f7/terms/SectionCompletion.java
+++ b/src/main/java/blade/addon/features/dungeon/f7/terms/SectionCompletion.java
@@ -6,7 +6,7 @@ import blade.addon.utils.events.Events;
 import blade.addon.utils.rendering.RenderUtils;
 import config.practical.hud.HUDComponent;
 import net.minecraft.ChatFormatting;
-import net.minecraft.client.gui.GuiGraphics;
+import net.minecraft.client.gui.GuiGraphicsExtractor;
 import net.minecraft.network.chat.Component;
 
 public class SectionCompletion {
@@ -25,7 +25,7 @@ public class SectionCompletion {
         return System.currentTimeMillis() - prevTime < 1000 && Floor7.sectionCompletionNotification;
     }
 
-    public static void render(HUDComponent component, GuiGraphics graphics) {
+    public static void render(HUDComponent component, GuiGraphicsExtractor graphics) {
         RenderUtils.drawCentere
…… diff 过长已截断 ……
- hopefully should be last rendering fixGabriel Ellnebrand · 2026-06-17 · cb7484d
查看变更
src/main/java/blade/addon/Bladeaddons.java                       |  1 +
 src/main/java/blade/addon/features/dungeon/DeathMessage.java     | 47 +++++++++++++++++++++++++++++++++++++++++++++++
 src/main/java/blade/addon/features/dungeon/f7/BossWaypoints.java | 11 ++++++++---
 src/main/java/blade/addon/features/other/PracticeSS.java         | 16 ++++++++--------
 src/main/java/blade/addon/utils/Waypoint.java                    |  8 ++++++++
 src/main/java/blade/addon/utils/config/Config.java               |  5 +++++
 src/main/java/blade/addon/utils/config/values/Dungeons.java      |  6 ++++++
 7 files changed, 83 insertions(+), 11 deletions(-)
diff --git a/src/main/java/blade/addon/Bladeaddons.java b/src/main/java/blade/addon/Bladeaddons.java
index 16ce65e..ed94832 100644
--- a/src/main/java/blade/addon/Bladeaddons.java
+++ b/src/main/java/blade/addon/Bladeaddons.java
@@ -127,5 +127,6 @@ public class Bladeaddons implements ModInitializer {
         CurrentSection.init();
         DragonTracer.init();
         ReaperDisplay.init();
+        //DeathMessage.init(); TODO finish this
     }
 }
\ No newline at end of file
diff --git a/src/main/java/blade/addon/features/dungeon/DeathMessage.java b/src/main/java/blade/addon/features/dungeon/DeathMessage.java
new file mode 100644
index 0000000..e70f34e
--- /dev/null
+++ b/src/main/java/blade/addon/features/dungeon/DeathMessage.java
@@ -0,0 +1,47 @@
+package blade.addon.features.dungeon;
+
+import blade.addon.utils.Location;
+import blade.addon.utils.config.values.Dungeons;
+import blade.addon.utils.events.Events;
+import net.minecraft.client.Minecraft;
+import net.minecraft.client.multiplayer.ClientPacketListener;
+
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+public class DeathMessage {
+
+    private static final Pattern[] patterns = new Pattern[]{
+        Pattern.compile("^ ☠ (.*) was killed by *."),
+        Pattern.compile("^ ☠ (.*) died to a *."),
+        Pattern.compile("^ ☠ (.*) fell to their death *."),
+    };
+
+    public static void init() {
+
+        Events.ON_GAME_MESSAGE.register(text -> {
+
+            if (!Location.inDungeon() || !Dungeons.sendDeathMessage) return false;
+
+            String msg = text.getString();
+            for(Pattern pattern: patterns) {
+                Matcher matcher = pattern.matcher(msg);
+                if (matcher.find()) {
+                    String name = matcher.group(1);
+
+                    ClientPacketListener networkHandler = Minecraft.getInstance().getConnection();
+                    if (networkHandler == null) return false;
+                    networkHandler.sendCommand("pc " + createMessage(name));
+                    return false;
+                }
+            }
+            return false;
+        });
+
+    }
+
+    private static String createMessage(String name) {
+        return Dungeons.deathMessage.replace("<player>", name);
+    }
+
+}
diff --git a/src/main/java/blade/addon/features/dungeon/f7/BossWaypoints.java b/src/main/java/blade/addon/features/dungeon/f7/BossWaypoints.java
index abb2fea..f5aa205 100644
--- a/src/main/java/blade/addon/features/dungeon/f7/BossWaypoints.java
+++ b/src/main/java/blade/addon/features/dungeon/f7/BossWaypoints.java
@@ -18,12 +18,16 @@ import com.mojang.blaze3d.vertex.VertexConsumer;
 import net.fabricmc.fabric.api.client.rendering.v1.world.WorldRenderContext;
 import net.fabricmc.fabric.api.event.player.UseBlockCallback;
 import net.minecraft.core.BlockPos;
+import net.minecraft.gizmos.GizmoStyle;
+import net.minecraft.gizmos.Gizmos;
 import net.minecraft.network.chat.Component;
+import net.minecraft.util.ARGB;
 import net.minecraft.world.InteractionHand;
 import net.minecraft.world.InteractionResult;
 import net.minecraft.world.entity.player.Player;
 import net.minecraft.world.level.Level;
 import net.minecraft.world.phys.BlockHitResult;
+
 import java.io.FileWriter;
 import java.io.IOException;
 import java.nio.file.Files;
@@ -137,7 +141,7 @@ public class BossWaypoints {
         return InteractionResult.PASS;
     }
 
-    private static void render(WorldRenderContext context, PoseStack matrixStack, VertexConsumer consumer) {
+    private static void renderThroughWall(WorldRenderContext context, PoseStack matrixStack, VertexConsumer consumer) {
         if (!isInValidArea()) return;
 
         waypoints.forEach(waypoint -> {
@@ -147,12 +151,13 @@ public class BossWaypoints {
         });
     }
 
-    private static void renderThroughWall(WorldRenderContext context, PoseStack matrixStack, VertexConsumer consumer) {
+    private static void render(WorldRenderContext context, PoseStack matrixStack, VertexConsumer consumer) {
         if (!isInValidArea()) return;
 
         waypoints.forEach(waypoint -> {
             if (!waypoint.isThroughWall()) {
-                waypoint.Render(consumer, matrixStack);
+                float[] color = waypoint.getColor();
+                Gizmos.cuboid(waypoint.getBox(), GizmoStyle.fill(ARGB.colorFromFloat(color[3], color[0],  color[1],  color[2])));
             }
         });
     }
diff --git a/src/main/java/blade/addon/features/other/PracticeSS.java b/src/main/java/blade/addon/features/other/PracticeSS.java
index 867b2c1..a0677d2 100644
--- a/src/main/java/blade/addon/features/other/PracticeSS.java
+++ b/src/main/java/blade/addon/features/other/PracticeSS.java
@@ -24,6 +24,7 @@ import net.minecraft.world.level.block.state.BlockState;
 import net.minecraft.world.level.block.state.properties.BlockStateProperties;
 import net.minecraft.world.phys.AABB;
 import net.minecraft.world.phys.BlockHitResult;
+
 import java.util.Collections;
 import java.util.List;
 import java.util.concurrent.CopyOnWriteArrayList;
@@ -352,15 +353,15 @@ public class PracticeSS {
         if (showingPattern) {
             if (!ExtraOptions.realisticDelay || totalTicks - ticksLeft > START_WAIT_DURATION || endIndex != 2) {
                 int lastIndex = getLastDisplayIndex();
-                renderButtons(matrixStack, consumer, 0, lastIndex);
-                renderBackground(matrixStack, consumer, lastIndex - 1);
+                renderButtons(0, lastIndex);
+                renderBackground(lastIndex - 1);
             }
         } else {
-            renderButtons(matrixStack, consumer, currentIndex, endIndex);
+            renderButtons(currentIndex, endIndex);
         }
     }
 
-    private static void renderButtons(PoseStack matrixStack, VertexConsumer consumer, int start, int end) {
+    private static void renderButtons(int start, int end) {
         AABB directionBox = getBox(direction);
         if (directionBox == null) return;
 
@@ -368,8 +369,7 @@ public class PracticeSS {
             if (buttons.size() <= i) break;
             BlockPos pos = buttons.get(i);
             AABB box = directionBox.move(pos.getX(), pos.getY(), pos.getZ());
-            float[] color = RenderUtils.toFloats(getColor(i));
-            RenderUtils.renderFilledBox(matrixStack, consumer, box, color);
+            RenderUtils.renderFilledBlock(box, getColor(i));
         }
     }
 
@@ -383,7 +383,7 @@ public class PracticeSS {
         };
     }
 
-    private static void renderBackground(PoseStack matrixStack, VertexConsumer consumer, int backgroundIndex) {
+    private static void renderBackground(int backgroundIndex) {
         if (buttons.size() > backgroundIndex && backgroundIndex >= 0) {
             AABB box = AABB.ofSize(buttons.get(backgroundIndex).getCenter(), 1, 1, 1);
             int dx, dz;
@@ -408,7 +408,7 @@ public class PracticeSS {
                     return;
                 }
             }
-            RenderUtils.renderFilledBox(matrixStack, consumer, box.move(dx, 0, dz), new float[]{0, 0.5f, 1, 1});
+            RenderUtils.renderFilledBlock(box.move(dx, 0, dz), 0xff0080ff);
         }
     }
 }
diff --git a/src/main/java/blade/addon/utils/Waypoint.java b/src/main/java/blade/addon/utils/Waypoint.java
index 183ab48..09e9929 100644
--- a/src/main/java/blade/addon/utils/Waypoint.java
+++ b/src/main/java/blade/addon/utils/Waypoint.java
@@ -42,6 +42,14 @@ public class Waypoint {
         RenderUtils.renderFilledBox(matrixStack, consumer, new AABB(x, y, z, x + dx, y + dy, z + dz), new float[]{r, g, b,a});
     }
 
+    public AABB getBox() {
+        return new AABB(x, y, z, x + dx, y + dy, z + dz);
+    }
+
+    public float[] getColor() {
+        return new float[]{r, g, b,a};
+    }
+
     @Override
     public String toString() {
         return "Waypoint{" +
diff --git a/src/main/java/blade/addon/utils/config/Config.java b/src/main/java/blade/addon/utils/config/Config.java
index 09e14cc..f60f09a 100644
--- a/src/main/java/blade/addon/utils/config/Config.java
+++ b/src/main/java/blade/addon/utils/config/Config.java
@@ -75,6 +75,11 @@ public class Config {
         start.add(new ConfigBool(Component.literal("Player count warning"), () -> Dungeons.detectPlayerCount, bool -> Dungeons.detectPlayerCount = bool));
         dungeons.add(start);
 
+        ConfigSection deathMessage = new ConfigSection(Component.literal("Death message"));
+        deathMessage.add(new ConfigBool(Component.literal("Send death message"), () -> Dungeons.sendDeathMessage, bool -> Dungeons.sendDeathMessage = bool));
+        deathMessage.add(new ConfigString(Component.literal("Message sent (<player> replaces with the players name) "), () -> Dungeons.deathMessage, str -> Dungeons.deathMessage = str));
+        dungeons.add(deathMessage);
+
         ConfigSection invincibility = new ConfigSection(Component.literal("Invincibility Timer"));
         invincibility.add(new ConfigBool(Component.literal("Enable invincibility display"), () -> Dungeons.displayInvincibilityTimer, bool -> Dungeons.displayInvincibilityTimer = bool));
         invincibility.add(new ConfigOptions<>(Component.literal("Display when"), InvincibilityTimer.DisplayWhen.values(), () -> Dungeons.displayWhen, when -> Dungeons.displayWhen = when));
diff --git a/src/main/java/blade/addon/utils/config/values/Dungeons.java b/src/main/java/blade/addon/utils/config/values/Dungeons.java
index 1c88caa..1dc0333 100644
--- a/src/main/java/blade/addon/utils/config/values/Dungeons.java
+++ b/src/main/java/blade/addon/utils/config/values/Dungeons.java
@@ -148,4 +148,10 @@ public class Dungeons {
 
     @ConfigValue
     public static boolean quizProgress = false;
+
+    @ConfigValue
+    public static boolean sendDeathMessage = false;
+
+    @ConfigValue
+    public static String deathMessage = "";
 }
- hopefully fixed itemhighlightGabriel Ellnebrand · 2026-06-10 · 279be4e
查看变更
src/main/java/blade/addon/features/dungeon/ItemHighlight.java | 6 ++----
 src/main/java/blade/addon/utils/rendering/RenderUtils.java    | 4 ++++
 2 files changed, 6 insertions(+), 4 deletions(-)
diff --git a/src/main/java/blade/addon/features/dungeon/ItemHighlight.java b/src/main/java/blade/addon/features/dungeon/ItemHighlight.java
index 8b4c883..47c6563 100644
--- a/src/main/java/blade/addon/features/dungeon/ItemHighlight.java
+++ b/src/main/java/blade/addon/features/dungeon/ItemHighlight.java
@@ -14,14 +14,12 @@ import net.fabricmc.fabric.api.client.rendering.v1.world.WorldRenderContext;
 import net.minecraft.client.Minecraft;
 import net.minecraft.client.multiplayer.ClientLevel;
 import net.minecraft.client.player.LocalPlayer;
-import net.minecraft.gizmos.GizmoStyle;
-import net.minecraft.gizmos.Gizmos;
 import net.minecraft.network.chat.Component;
-import net.minecraft.util.ARGB;
 import net.minecraft.util.Mth;
 import net.minecraft.world.entity.EntityDimensions;
 import net.minecraft.world.entity.item.ItemEntity;
 import net.minecraft.world.phys.AABB;
+
 import java.util.HashSet;
 import java.util.List;
 import java.util.concurrent.ConcurrentHashMap;
@@ -72,7 +70,7 @@ public class ItemHighlight {
             EntityDimensions dimension = itemEntity.getDimensions(itemEntity.getPose());
             AABB box = dimension.makeBoundingBox(x, y, z).inflate(0.1).move(0, 0.05, 0);
             float[] color = RenderUtils.toFloats(getColor(itemEntity));
-            Gizmos.cuboid(box, GizmoStyle.fill(ARGB.colorFromFloat(color[3], color[0], color[1], color[2])));
+            RenderUtils.renderFilledBox(matrixStack, consumer, box, color);
         });
     }
 
diff --git a/src/main/java/blade/addon/utils/rendering/RenderUtils.java b/src/main/java/blade/addon/utils/rendering/RenderUtils.java
index 24325e6..c1f0490 100644
--- a/src/main/java/blade/addon/utils/rendering/RenderUtils.java
+++ b/src/main/java/blade/addon/utils/rendering/RenderUtils.java
@@ -13,6 +13,7 @@ import net.minecraft.client.Minecraft;
 import net.minecraft.client.gui.Font;
 import net.minecraft.client.gui.GuiGraphics;
 import net.minecraft.client.player.LocalPlayer;
+import net.minecraft.gizmos.GizmoStyle;
 import net.minecraft.gizmos.Gizmos;
 import net.minecraft.network.chat.Component;
 import net.minecraft.world.phys.AABB;
@@ -273,4 +274,7 @@ public class RenderUtils {
 
     }
 
+    public static void renderFilledBlock(AABB box, int argb) {
+        Gizmos.cuboid(box, GizmoStyle.fill(argb));
+    }
 }
- changed some rendering i think, dont fully remember i made commit a week ago and forgot to commit (p sure it still has issues but whatever)Gabriel Ellnebrand · 2026-06-10 · 201b04a
查看变更
src/main/java/blade/addon/features/other/SearchBar.java         |  2 +-
 src/main/java/blade/addon/utils/Waypoint.java                   |  6 ++----
 src/main/java/blade/addon/utils/config/Config.java              | 19 ++++++-------------
 src/main/java/blade/addon/utils/config/values/ExtraOptions.java |  6 +++---
 src/main/java/blade/addon/utils/rendering/RenderLayers.java     |  3 +++
 src/main/java/blade/addon/utils/rendering/RenderPipelines.java  |  6 ++++++
 src/main/java/blade/addon/utils/rendering/RenderingEvents.java  |  2 ++
 7 files changed, 23 insertions(+), 21 deletions(-)
diff --git a/src/main/java/blade/addon/features/other/SearchBar.java b/src/main/java/blade/addon/features/other/SearchBar.java
index 2819a29..31a7353 100644
--- a/src/main/java/blade/addon/features/other/SearchBar.java
+++ b/src/main/java/blade/addon/features/other/SearchBar.java
@@ -31,7 +31,7 @@ public class SearchBar {
         DrawEvents.INVENTORY_SLOT_AFTER.register((context, item, x, y) -> {
             if (shouldDisplay() && !searchTerm.isEmpty() && ExtraOptions.toggleableSearchBar && Double.isNaN(parsedValue)) {
                 if (!matches(item)) {
-                    context.fill(x, y, x + 16, y + 16, 0xaa111111);
+                    context.fill(x, y, x + 16, y + 16, ExtraOptions.searchbarMissColor);
                 }
 
             }
diff --git a/src/main/java/blade/addon/utils/Waypoint.java b/src/main/java/blade/addon/utils/Waypoint.java
index bf7cbc7..183ab48 100644
--- a/src/main/java/blade/addon/utils/Waypoint.java
+++ b/src/main/java/blade/addon/utils/Waypoint.java
@@ -1,10 +1,8 @@
 package blade.addon.utils;
 
+import blade.addon.utils.rendering.RenderUtils;
 import com.mojang.blaze3d.vertex.PoseStack;
 import com.mojang.blaze3d.vertex.VertexConsumer;
-import net.minecraft.gizmos.GizmoStyle;
-import net.minecraft.gizmos.Gizmos;
-import net.minecraft.util.ARGB;
 import net.minecraft.world.phys.AABB;
 
 public class Waypoint {
@@ -41,7 +39,7 @@ public class Waypoint {
     }
 
     public void Render(VertexConsumer consumer, PoseStack matrixStack) {
-        Gizmos.cuboid(new AABB(x, y, z, x + dx, y + dy, z + dz), GizmoStyle.fill(ARGB.colorFromFloat(a, r, g, b)));
+        RenderUtils.renderFilledBox(matrixStack, consumer, new AABB(x, y, z, x + dx, y + dy, z + dz), new float[]{r, g, b,a});
     }
 
     @Override
diff --git a/src/main/java/blade/addon/utils/config/Config.java b/src/main/java/blade/addon/utils/config/Config.java
index 326d14e..09e14cc 100644
--- a/src/main/java/blade/addon/utils/config/Config.java
+++ b/src/main/java/blade/addon/utils/config/Config.java
@@ -7,35 +7,28 @@ import blade.addon.features.dungeon.f7.location.LocationNotifier;
 import blade.addon.features.filter.FilterList;
 import blade.addon.features.highlight.MobHighlight;
 import blade.addon.features.item.ProtectItem;
-import blade.addon.features.other.DianaNotifier;
 import blade.addon.features.notifications.NotificationList;
+import blade.addon.features.other.DianaNotifier;
 import blade.addon.utils.config.components.Components;
-import blade.addon.utils.config.values.Buttons;
-import blade.addon.utils.config.values.Dungeons;
-import blade.addon.utils.config.values.ExtraOptions;
-import blade.addon.utils.config.values.Floor7;
-import blade.addon.utils.config.values.Visual;
+import blade.addon.utils.config.values.*;
 import blade.addon.utils.dungeon.Phase;
 import blade.addon.utils.dungeon.Section;
 import blade.addon.utils.dungeon.Split;
 import config.practical.ConfigurableScreen;
 import config.practical.category.ConfigCategory;
 import config.practical.manager.ConfigManager;
-import config.practical.widgets.ConfigBool;
-import config.practical.widgets.ConfigButton;
-import config.practical.widgets.ConfigSection;
-import config.practical.widgets.ConfigString;
-import config.practical.widgets.ConfigTextArea;
+import config.practical.widgets.*;
 import config.practical.widgets.color.ConfigColor;
 import config.practical.widgets.options.ConfigOptions;
 import config.practical.widgets.sliders.ConfigDouble;
 import config.practical.widgets.sliders.ConfigInt;
 import config.practical.widgets.sound.ConfigSound;
-import java.util.List;
 import net.minecraft.client.Minecraft;
 import net.minecraft.client.gui.screens.Screen;
 import net.minecraft.network.chat.Component;
 
+import java.util.List;
+
 public class Config {
 
     private static final Component TITLE = Component.literal("Blade Addons");
@@ -323,9 +316,9 @@ public class Config {
         extra.add(new ConfigBool(Component.literal("Display selected arrow"), () -> ExtraOptions.displayCurrentArrow, bool -> ExtraOptions.displayCurrentArrow = bool));
         extra.add(new ConfigBool(Component.literal("Title on arrow swap"), () -> ExtraOptions.arrowSwapNotification, bool -> ExtraOptions.arrowSwapNotification = bool));
         extra.add(new ConfigBool(Component.literal("Toggleable searchbar (ctrl + f)"), () -> ExtraOptions.toggleableSearchBar, bool -> ExtraOptions.toggleableSearchBar = bool));
+        extra.add(new ConfigColor(Component.literal("Searchbar miss color"), () -> ExtraOptions.searchbarMissColor, color -> ExtraOptions.searchbarMissColor = color, "search-bar-miss-color", true));
         extra.add(new ConfigBool(Component.literal("Disable protect item (resets on launch)"), () -> ProtectItem.stopProtectItem, bool -> ProtectItem.stopProtectItem = bool));
         extra.add(new ConfigBool(Component.literal("Display reaper duration"), () -> ExtraOptions.enableReaperDisplay, bool -> ExtraOptions.enableReaperDisplay = bool));
-        //extra.add(new ConfigBool(Text.literal("Kuudra stun waypoint"), () -> ExtraOptions.stunWaypoint, bool -> ExtraOptions.stunWaypoint = bool));
 
 
         ConfigSection sound = new ConfigSection(Component.literal("Sound options"));
diff --git a/src/main/java/blade/addon/utils/config/values/ExtraOptions.java b/src/main/java/blade/addon/utils/config/values/ExtraOptions.java
index 35386ea..891f6a9 100644
--- a/src/main/java/blade/addon/utils/config/values/ExtraOptions.java
+++ b/src/main/java/blade/addon/utils/config/values/ExtraOptions.java
@@ -112,9 +112,6 @@ public class ExtraOptions {
     @ConfigValue
     public static boolean arrowSwapNotification = false;
 
-    @ConfigValue
-    public static boolean stunWaypoint = false;
-
     @ConfigValue
     public static int petHighlightColor = 0xffff0000;
 
@@ -141,4 +138,7 @@ public class ExtraOptions {
 
     @ConfigValue
     public static boolean disableAllFilters = false;
+
+    @ConfigValue
+    public static int searchbarMissColor = 0xaa111111;
 }
diff --git a/src/main/java/blade/addon/utils/rendering/RenderLayers.java b/src/main/java/blade/addon/utils/rendering/RenderLayers.java
index f7c51cb..68ecf08 100644
--- a/src/main/java/blade/addon/utils/rendering/RenderLayers.java
+++ b/src/main/java/blade/addon/utils/rendering/RenderLayers.java
@@ -9,6 +9,9 @@ public class RenderLayers {
     public static final RenderType FILLED_DEBUG =
             RenderType.create("filled-debug-layer", RenderSetup.builder(RenderPipelines.FILLED_DEBUG).createRenderSetup());
 
+    public static final RenderType FILLED_DEBUG_NO_DEPTH =
+            RenderType.create("filled-debug-no-depth-layer", RenderSetup.builder(RenderPipelines.FILLED_DEBUG).createRenderSetup());
+
     public static final RenderType FILLED =
             RenderType.create("filled-layer", RenderSetup.builder(RenderPipelines.FILLED).createRenderSetup());
 
diff --git a/src/main/java/blade/addon/utils/rendering/RenderPipelines.java b/src/main/java/blade/addon/utils/rendering/RenderPipelines.java
index 0f3e85e..90232af 100644
--- a/src/main/java/blade/addon/utils/rendering/RenderPipelines.java
+++ b/src/main/java/blade/addon/utils/rendering/RenderPipelines.java
@@ -11,6 +11,12 @@ public class RenderPipelines {
     public static final RenderPipeline FILLED_DEBUG = net.minecraft.client.renderer.RenderPipelines.register(RenderPipeline.builder(net.minecraft.client.renderer.RenderPipelines.DEBUG_FILLED_SNIPPET)
             .withLocation(Identifier.fromNamespaceAndPath(Constants.NAMESPACE, "filled-debug-pipe"))
             .withVertexFormat(DefaultVertexFormat.POSITION_COLOR, VertexFormat.Mode.QUADS)
+            .withDepthWrite(true)
+            .build());
+
+    public static final RenderPipeline FILLED_DEBUG_NO_DEPTH = net.minecraft.client.renderer.RenderPipelines.register(RenderPipeline.builder(net.minecraft.client.renderer.RenderPipelines.DEBUG_FILLED_SNIPPET)
+            .withLocation(Identifier.fromNamespaceAndPath(Constants.NAMESPACE, "filled-debug-no-depth-pipe"))
+            .withVertexFormat(DefaultVertexFormat.POSITION_COLOR, VertexFormat.Mode.QUADS)
             .build());
 
 
diff --git a/src/main/java/blade/addon/utils/rendering/RenderingEvents.java b/src/main/java/blade/addon/utils/rendering/RenderingEvents.java
index 0e48b9d..8efb1f2 100644
--- a/src/main/java/blade/addon/utils/rendering/RenderingEvents.java
+++ b/src/main/java/blade/addon/utils/rendering/RenderingEvents.java
@@ -5,6 +5,7 @@ import net.fabricmc.fabric.api.client.rendering.v1.world.WorldRenderEvents;
 public class RenderingEvents {
 
     public static RenderHandler FILLED_DEBUG = new RenderHandler(RenderLayers.FILLED_DEBUG);
+    public static RenderHandler FILLED_DEBUG_NO_DEPTH = new RenderHandler(RenderLayers.FILLED_DEBUG_NO_DEPTH);
     public static RenderHandler FILLED = new RenderHandler(RenderLayers.FILLED);
     public static RenderHandler LINE = new RenderHandler(RenderLayers.LINE);
     public static RenderHandler FILLED_NO_DEPTH = new RenderHandler(RenderLayers.FILLED_NO_DEPTH);
@@ -13,6 +14,7 @@ public class RenderingEvents {
 
     public static void init() {
         WorldRenderEvents.BEFORE_DEBUG_RENDER.register(FILLED_DEBUG::init);
+        WorldRenderEvents.BEFORE_DEBUG_RENDER.register(FILLED_DEBUG_NO_DEPTH::init);
         WorldRenderEvents.AFTER_ENTITIES.register(FILLED::init);
         WorldRenderEvents.AFTER_ENTITIES.register(LINE::init);
         WorldRenderEvents.AFTER_ENTITIES.register(FILLED_NO_DEPTH::init);
- fixed identifier naming for renderlayers and renderpipelinesGabriel Ellnebrand · 2026-05-17 · 85f4c58
查看变更
src/main/java/blade/addon/utils/rendering/RenderLayers.java    | 10 +++++-----
 src/main/java/blade/addon/utils/rendering/RenderPipelines.java | 10 +++++-----
 2 files changed, 10 insertions(+), 10 deletions(-)
diff --git a/src/main/java/blade/addon/utils/rendering/RenderLayers.java b/src/main/java/blade/addon/utils/rendering/RenderLayers.java
index 7d503d0..f7c51cb 100644
--- a/src/main/java/blade/addon/utils/rendering/RenderLayers.java
+++ b/src/main/java/blade/addon/utils/rendering/RenderLayers.java
@@ -7,20 +7,20 @@ import net.minecraft.client.renderer.rendertype.RenderType;
 public class RenderLayers {
 
     public static final RenderType FILLED_DEBUG =
-            RenderType.create("filled-debug", RenderSetup.builder(RenderPipelines.FILLED_DEBUG).createRenderSetup());
+            RenderType.create("filled-debug-layer", RenderSetup.builder(RenderPipelines.FILLED_DEBUG).createRenderSetup());
 
     public static final RenderType FILLED =
-            RenderType.create("filled", RenderSetup.builder(RenderPipelines.FILLED).createRenderSetup());
+            RenderType.create("filled-layer", RenderSetup.builder(RenderPipelines.FILLED).createRenderSetup());
 
     public static final RenderType LINE =
-            RenderType.create("line", RenderSetup.builder(RenderPipelines.LINE).setOutline(RenderSetup.OutlineProperty.IS_OUTLINE).setLayeringTransform(LayeringTransform.VIEW_OFFSET_Z_LAYERING).createRenderSetup());
+            RenderType.create("line-layer", RenderSetup.builder(RenderPipelines.LINE).setOutline(RenderSetup.OutlineProperty.IS_OUTLINE).setLayeringTransform(LayeringTransform.VIEW_OFFSET_Z_LAYERING).createRenderSetup());
 
 
     public static final RenderType FILLED_NO_DEPTH =
-            RenderType.create("filled-no-depth", RenderSetup.builder(RenderPipelines.FILLED_NO_DEPTH).createRenderSetup());
+            RenderType.create("filled-no-depth-layer", RenderSetup.builder(RenderPipelines.FILLED_NO_DEPTH).createRenderSetup());
 
     public static final RenderType LINE_NO_DEPTH =
-            RenderType.create("line-no-depth", RenderSetup.builder(RenderPipelines.LINE_NO_DEPTH).setOutline(RenderSetup.OutlineProperty.IS_OUTLINE).setLayeringTransform(LayeringTransform.VIEW_OFFSET_Z_LAYERING).createRenderSetup());
+            RenderType.create("line-no-depth-layer", RenderSetup.builder(RenderPipelines.LINE_NO_DEPTH).setOutline(RenderSetup.OutlineProperty.IS_OUTLINE).setLayeringTransform(LayeringTransform.VIEW_OFFSET_Z_LAYERING).createRenderSetup());
 
 
 }
diff --git a/src/main/java/blade/addon/utils/rendering/RenderPipelines.java b/src/main/java/blade/addon/utils/rendering/RenderPipelines.java
index 217e07a..0f3e85e 100644
--- a/src/main/java/blade/addon/utils/rendering/RenderPipelines.java
+++ b/src/main/java/blade/addon/utils/rendering/RenderPipelines.java
@@ -9,34 +9,34 @@ import net.minecraft.resources.Identifier;
 
 public class RenderPipelines {
     public static final RenderPipeline FILLED_DEBUG = net.minecraft.client.renderer.RenderPipelines.register(RenderPipeline.builder(net.minecraft.client.renderer.RenderPipelines.DEBUG_FILLED_SNIPPET)
-            .withLocation(Identifier.fromNamespaceAndPath(Constants.NAMESPACE, "filled-debug"))
+            .withLocation(Identifier.fromNamespaceAndPath(Constants.NAMESPACE, "filled-debug-pipe"))
             .withVertexFormat(DefaultVertexFormat.POSITION_COLOR, VertexFormat.Mode.QUADS)
             .build());
 
 
 
     public static final RenderPipeline FILLED = net.minecraft.client.renderer.RenderPipelines.register(RenderPipeline.builder(net.minecraft.client.renderer.RenderPipelines.DEBUG_FILLED_SNIPPET)
-            .withLocation(Identifier.fromNamespaceAndPath(Constants.NAMESPACE, "filled"))
+            .withLocation(Identifier.fromNamespaceAndPath(Constants.NAMESPACE, "filled-pipe"))
             .withVertexFormat(DefaultVertexFormat.POSITION_COLOR, VertexFormat.Mode.QUADS)
             .withDepthWrite(true)
             .build());
 
     public static final RenderPipeline LINE = net.minecraft.client.renderer.RenderPipelines.register(RenderPipeline.builder(net.minecraft.client.renderer.RenderPipelines.LINES_SNIPPET)
-            .withLocation(Identifier.fromNamespaceAndPath(Constants.NAMESPACE, "line"))
+            .withLocation(Identifier.fromNamespaceAndPath(Constants.NAMESPACE, "line-pipe"))
             .withVertexFormat(DefaultVertexFormat.POSITION_COLOR_NORMAL_LINE_WIDTH, VertexFormat.Mode.LINES)
             .withDepthWrite(true)
             .build());
 
 
     public static final RenderPipeline FILLED_NO_DEPTH = net.minecraft.client.renderer.RenderPipelines.register(RenderPipeline.builder(net.minecraft.client.renderer.RenderPipelines.DEBUG_FILLED_SNIPPET)
-            .withLocation(Identifier.fromNamespaceAndPath(Constants.NAMESPACE, "filled-no-depth"))
+            .withLocation(Identifier.fromNamespaceAndPath(Constants.NAMESPACE, "filled-no-depth-pipe"))
             .withVertexFormat(DefaultVertexFormat.POSITION_COLOR, VertexFormat.Mode.QUADS)
             .withDepthTestFunction(DepthTestFunction.NO_DEPTH_TEST)
             .build());
 
 
     public static final RenderPipeline LINE_NO_DEPTH = net.minecraft.client.renderer.RenderPipelines.register(RenderPipeline.builder(net.minecraft.client.renderer.RenderPipelines.LINES_SNIPPET)
-            .withLocation(Identifier.fromNamespaceAndPath(blade.addon.utils.Constants.NAMESPACE, "line-no-depth"))
+            .withLocation(Identifier.fromNamespaceAndPath(blade.addon.utils.Constants.NAMESPACE, "line-no-depth-pipe"))
             .withVertexFormat(DefaultVertexFormat.POSITION_COLOR_NORMAL_LINE_WIDTH, VertexFormat.Mode.LINES)
             .withDepthTestFunction(DepthTestFunction.NO_DEPTH_TEST)
             .build());
- remade renderingGabriel Ellnebrand · 2026-05-17 · 8953995
查看变更
src/main/java/blade/addon/features/dungeon/ItemHighlight.java                |   2 +-
 src/main/java/blade/addon/features/dungeon/f7/BossWaypoints.java             |   5 +--
 src/main/java/blade/addon/features/dungeon/f7/RelicTimer.java                |   2 +-
 src/main/java/blade/addon/features/dungeon/f7/dragons/DragonHealth.java      |   2 +-
 src/main/java/blade/addon/features/dungeon/f7/location/PositionMessages.java |   3 +-
 src/main/java/blade/addon/features/highlight/BatHighlight.java               |  11 +++---
 src/main/java/blade/addon/features/highlight/MimicHighlight.java             |   8 ++--
 src/main/java/blade/addon/features/highlight/MobHighlight.java               |   8 ++--
 src/main/java/blade/addon/features/highlight/SheepHighlight.java             |   8 ++--
 src/main/java/blade/addon/features/highlight/TeammateHighlight.java          |   4 +-
 src/main/java/blade/addon/features/highlight/WitherHighlight.java            |   8 ++--
 src/main/java/blade/addon/features/other/PracticeSS.java                     |  16 ++++----
 src/main/java/blade/addon/utils/rendering/RenderHandler.java                 |  31 +++++++++++++++
 src/main/java/blade/addon/utils/rendering/RenderLayers.java                  |  45 +++++----------------
 src/main/java/blade/addon/utils/rendering/RenderPipelines.java               |  35 ++++++++++-------
 src/main/java/blade/addon/utils/rendering/RenderUtils.java                   | 160 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++---------
 src/main/java/blade/addon/utils/rendering/RenderingEvents.java               | 134 +++++---------------------------------------------------------
 17 files changed, 251 insertions(+), 231 deletions(-)
diff --git a/src/main/java/blade/addon/features/dungeon/ItemHighlight.java b/src/main/java/blade/addon/features/dungeon/ItemHighlight.java
index f327c92..8b4c883 100644
--- a/src/main/java/blade/addon/features/dungeon/ItemHighlight.java
+++ b/src/main/java/blade/addon/features/dungeon/ItemHighlight.java
@@ -56,7 +56,7 @@ public class ItemHighlight {
             }
         });
 
-        RenderingEvents.NO_DEPTH_FILLED.register(ItemHighlight::render);
+        RenderingEvents.FILLED_NO_DEPTH.register(ItemHighlight::render);
     }
 
     private static void render(WorldRenderContext context, PoseStack matrixStack, VertexConsumer consumer) {
diff --git a/src/main/java/blade/addon/features/dungeon/f7/BossWaypoints.java b/src/main/java/blade/addon/features/dungeon/f7/BossWaypoints.java
index 4e59751..abb2fea 100644
--- a/src/main/java/blade/addon/features/dungeon/f7/BossWaypoints.java
+++ b/src/main/java/blade/addon/features/dungeon/f7/BossWaypoints.java
@@ -42,9 +42,8 @@ public class BossWaypoints {
 
     public static void init() {
         load();
-        //needs separate consumers because of depth checking or smth
-        RenderingEvents.FILLED_BLOCK.register(BossWaypoints::render);
-        RenderingEvents.NO_DEPTH_FILLED.register(BossWaypoints::renderThroughWall);
+        RenderingEvents.FILLED.register(BossWaypoints::render);
+        RenderingEvents.FILLED_NO_DEPTH.register(BossWaypoints::renderThroughWall);
         UseBlockCallback.EVENT.register(BossWaypoints::onBlock);
     }
 
diff --git a/src/main/java/blade/addon/features/dungeon/f7/RelicTimer.java b/src/main/java/blade/addon/features/dungeon/f7/RelicTimer.java
index daa107b..3d47bad 100644
--- a/src/main/java/blade/addon/features/dungeon/f7/RelicTimer.java
+++ b/src/main/java/blade/addon/features/dungeon/f7/RelicTimer.java
@@ -157,7 +157,7 @@ public class RelicTimer {
 
 
         Events.ON_BLOCK_INTERACTION.register(RelicTimer::blockInteraction);
-        RenderingEvents.FILLED_BLOCK.register(RelicTimer::worldRender);
+        RenderingEvents.FILLED.register(RelicTimer::worldRender);
 
     }
 
diff --git a/src/main/java/blade/addon/features/dungeon/f7/dragons/DragonHealth.java b/src/main/java/blade/addon/features/dungeon/f7/dragons/DragonHealth.java
index cbf6662..dfecef1 100644
--- a/src/main/java/blade/addon/features/dungeon/f7/dragons/DragonHealth.java
+++ b/src/main/java/blade/addon/features/dungeon/f7/dragons/DragonHealth.java
@@ -43,7 +43,7 @@ public class DragonHealth {
         });
 
         ClientTickEvents.END_WORLD_TICK.register(world -> dragons.removeIf(dataHolder -> dataHolder.dragon.isRemoved()));
-        RenderingEvents.FILLED_ENTITY.register(DragonHealth::render);
+        RenderingEvents.FILLED.register(DragonHealth::render);
         Events.ON_LOCATION_CHANGE.register(newLocation -> {
             reset();
             return false;
diff --git a/src/main/java/blade/addon/features/dungeon/f7/location/PositionMessages.java b/src/main/java/blade/addon/features/dungeon/f7/location/PositionMessages.java
index ecb3313..fcc6af0 100644
--- a/src/main/java/blade/addon/features/dungeon/f7/location/PositionMessages.java
+++ b/src/main/java/blade/addon/features/dungeon/f7/location/PositionMessages.java
@@ -35,7 +35,7 @@ public class PositionMessages {
 
     public static void init() {
         ClientTickEvents.END_CLIENT_TICK.register(PositionMessages::tick);
-        RenderingEvents.OUTLINE_ENTITY.register(PositionMessages::render);
+        RenderingEvents.LINE.register(PositionMessages::render);
         Events.ON_LOCATION_CHANGE.register(newLocation -> {
             disableAll();
             return false;
@@ -100,7 +100,6 @@ public class PositionMessages {
     private static void render(WorldRenderContext context, PoseStack matrixStack, VertexConsumer consumer) {
         if (!Location.inDungeon() || !Debug.renderPositions) return;
 
-        PoseStack.Pose entry = matrixStack.last();
         for (PositionMessage positionMessage : positionMessages) {
             AABB box = positionMessage.getBox();
             if (positionMessage.sent()) {
diff --git a/src/main/java/blade/addon/features/highlight/BatHighlight.java b/src/main/java/blade/addon/features/highlight/BatHighlight.java
index 74f598b..f6bb712 100644
--- a/src/main/java/blade/addon/features/highlight/BatHighlight.java
+++ b/src/main/java/blade/addon/features/highlight/BatHighlight.java
@@ -10,6 +10,7 @@ import com.mojang.blaze3d.vertex.VertexConsumer;
 import net.fabricmc.fabric.api.client.event.lifecycle.v1.ClientTickEvents;
 import net.fabricmc.fabric.api.client.rendering.v1.world.WorldRenderContext;
 import net.minecraft.world.entity.ambient.Bat;
+
 import java.util.concurrent.ConcurrentLinkedQueue;
 
 public class BatHighlight {
@@ -37,24 +38,24 @@ public class BatHighlight {
             return false;
         });
 
-        ClientTickEvents.END_CLIENT_TICK.register(client -> bats.removeIf(bat -> bat.isRemoved() ||bat.isDeadOrDying()));
+        ClientTickEvents.END_CLIENT_TICK.register(client -> bats.removeIf(bat -> bat.isRemoved() || bat.isDeadOrDying()));
 
-        RenderingEvents.FILLED_ENTITY.register(BatHighlight::renderFilled);
-        RenderingEvents.OUTLINE_ENTITY.register(BatHighlight::renderOutline);
+        RenderingEvents.FILLED.register(BatHighlight::renderFilled);
+        RenderingEvents.LINE.register(BatHighlight::renderOutline);
     }
 
     private static void renderFilled(WorldRenderContext context, PoseStack matrixStack, VertexConsumer consumer) {
         if (!MobHighlight.mobHighlight || !MobHighlight.renderFilled()) return;
 
         float[] rgba = RenderUtils.toFloats(MobHighlight.batFilledColor);
-        bats.forEach(entity -> RenderUtils.renderFilled(EntityUtil.getBox(entity), rgba));
+        bats.forEach(entity -> RenderUtils.renderFilledBox(matrixStack, consumer, EntityUtil.getBox(entity), rgba));
     }
 
     private static void renderOutline(WorldRenderContext context, PoseStack matrixStack, VertexConsumer consumer) {
         if (!MobHighlight.mobHighlight || !MobHighlight.renderOutline()) return;
 
         float[] rgba = RenderUtils.toFloats(MobHighlight.batOutlineColor);
-        bats.forEach(entity -> RenderUtils.renderOutline(EntityUtil.getBox(entity), rgba));
+        bats.forEach(entity -> RenderUtils.renderOutlinedBox(matrixStack, consumer, EntityUtil.getBox(entity), rgba));
     }
 
 }
diff --git a/src/main/java/blade/addon/features/highlight/MimicHighlight.java b/src/main/java/blade/addon/features/highlight/MimicHighlight.java
index 440cbe6..c190081 100644
--- a/src/main/java/blade/addon/features/highlight/MimicHighlight.java
+++ b/src/main/java/blade/addon/features/highlight/MimicHighlight.java
@@ -36,8 +36,8 @@ public class MimicHighlight {
 
        ClientTickEvents.END_CLIENT_TICK.register(client -> mimics.removeIf(BlockEntity::isRemoved));
 
-        RenderingEvents.FILLED_ENTITY.register(MimicHighlight::renderFilled);
-        RenderingEvents.OUTLINE_ENTITY.register(MimicHighlight::renderOutline);
+        RenderingEvents.FILLED.register(MimicHighlight::renderFilled);
+        RenderingEvents.LINE.register(MimicHighlight::renderOutline);
     }
 
 
@@ -45,13 +45,13 @@ public class MimicHighlight {
         if (!MobHighlight.highlightMimicChests || !MobHighlight.renderFilled()) return;
 
         float[] rgba = RenderUtils.toFloats(MobHighlight.mimicFilledColor);
-        mimics.forEach(mimic -> RenderUtils.renderFilled(box.move(mimic.getBlockPos()), rgba));
+        mimics.forEach(mimic -> RenderUtils.renderFilledBox(matrixStack, consumer, box.move(mimic.getBlockPos()), rgba));
     }
 
     private static void renderOutline(WorldRenderContext context, PoseStack matrixStack, VertexConsumer consumer) {
         if (!MobHighlight.highlightMimicChests || !MobHighlight.renderOutline()) return;
 
         float[] rgba = RenderUtils.toFloats(MobHighlight.mimicOutlineColor);
-        mimics.forEach(mimic -> RenderUtils.renderOutline(box.move(mimic.getBlockPos()), rgba));
+        mimics.forEach(mimic -> RenderUtils.renderOutlinedBox(matrixStack, consumer,box.move(mimic.getBlockPos()), rgba));
     }
 }
diff --git a/src/main/java/blade/addon/features/highlight/MobHighlight.java b/src/main/java/blade/addon/features/highlight/MobHighlight.java
index bc70689..c29ca11 100644
--- a/src/main/java/blade/addon/features/highlight/MobHighlight.java
+++ b/src/main/java/blade/addon/features/highlight/MobHighlight.java
@@ -165,8 +165,8 @@ public class MobHighlight {
             }
         });
 
-        RenderingEvents.FILLED_ENTITY.register(MobHighlight::renderFilled);
-        RenderingEvents.OUTLINE_ENTITY.register(MobHighlight::renderOutline);
+        RenderingEvents.FILLED.register(MobHighlight::renderFilled);
+        RenderingEvents.LINE.register(MobHighlight::renderOutline);
     }
 
     private static void testArmorStand(ArmorStand armorStand) {
@@ -314,7 +314,7 @@ public class MobHighlight {
             int filledColor = getFilledColor(type);
             float[] rgba = RenderUtils.toFloats(filledColor);
 
-            RenderUtils.renderFilled(box, rgba);
+            RenderUtils.renderFilledBox(matrixStack, consumer,box, rgba);
 
         }
     }
@@ -332,7 +332,7 @@ public class MobHighlight {
             int outlineColor = getOutlineColor(type);
             float[] rgba = RenderUtils.toFloats(outlineColor);
 
-            RenderUtils.renderOutline(box, rgba);
+            RenderUtils.renderOutlinedBox(matrixStack, consumer,box, rgba);
         }
     }
 }
diff --git a/src/main/java/blade/addon/features/highlight/SheepHighlight.java b/src/main/java/blade/addon/features/highlight/SheepHighlight.java
index 7a4308d..fed9925 100644
--- a/src/main/java/blade/addon/features/highlight/SheepHighlight.java
+++ b/src/main/java/blade/addon/features/highlight/SheepHighlight.java
@@ -33,22 +33,22 @@ public class SheepHighlight {
 
         ClientTickEvents.END_CLIENT_TICK.register(client -> sheeps.removeIf(sheep -> sheep.isRemoved() || sheep.isDeadOrDying()));
 
-        RenderingEvents.FILLED_ENTITY.register(SheepHighlight::renderFilled);
-        RenderingEvents.OUTLINE_ENTITY.register(SheepHighlight::renderOutline);
+        RenderingEvents.FILLED.register(SheepHighlight::renderFilled);
+        RenderingEvents.LINE.register(SheepHighlight::renderOutline);
     }
 
     private static void renderFilled(WorldRenderContext context, PoseStack matrixStack, VertexConsumer consumer) {
         if (!MobHighlight.highlightSheep || !MobHighlight.renderFilled()) return;
 
         float[] rgba = RenderUtils.toFloats(MobHighlight.sheepFilledColor);
-        sheeps.forEach(entity -> RenderUtils.renderFilled(EntityUtil.getBox(entity), rgba));
+        sheeps.forEach(entity -> RenderUtils.renderFilledBox(matrixStack, consumer, EntityUtil.getBox(entity), rgba));
     }
 
     private static void renderOutline(WorldRenderContext context, PoseStack matrixStack, VertexConsumer consumer) {
         if (!MobHighlight.highlightSheep || !MobHighlight.renderOutline()) return;
 
         float[] rgba = RenderUtils.toFloats(MobHighlight.sheepFilledColor);
-        sheeps.forEach(entity -> RenderUtils.renderOutline(EntityUtil.getBox(entity), rgba));
+        sheeps.forEach(entity -> RenderUtils.renderOutlinedBox(matrixStack, consumer,EntityUtil.getBox(entity), rgba));
     }
 
 }
diff --git a/src/main/java/blade/addon/features/highlight/TeammateHighlight.java b/src/main/java/blade/addon/features/highlight/TeammateHighlight.java
index 5c6947e..3b9181e 100644
--- a/src/main/java/blade/addon/features/highlight/TeammateHighlight.java
+++ b/src/main/java/blade/addon/features/highlight/TeammateHighlight.java
@@ -42,7 +42,7 @@ public class TeammateHighlight {
 
         ClientTickEvents.END_CLIENT_TICK.register(client -> teammates.removeIf(Entity::isRemoved));
 
-        RenderingEvents.NO_DEPTH_OUTLINE_ENTITY.register(TeammateHighlight::renderOutline);
+        RenderingEvents.LINE_NO_DEPTH.register(TeammateHighlight::renderOutline);
     }
 
     private static void renderOutline(WorldRenderContext context, PoseStack matrixStack, VertexConsumer consumer) {
@@ -57,7 +57,7 @@ public class TeammateHighlight {
 
             if (Dungeons.highlightTeammates) {
                 float[] rgba = RenderUtils.toFloats(color);
-                RenderUtils.renderOutline(EntityUtil.getBox(player), rgba);
+                RenderUtils.renderOutlinedBox(matrixStack, consumer,EntityUtil.getBox(player), rgba);
             }
 
             if (Dungeons.renderClassName) {
diff --git a/src/main/java/blade/addon/features/highlight/WitherHighlight.java b/src/main/java/blade/addon/features/highlight/WitherHighlight.java
index b855cf2..e273d2c 100644
--- a/src/main/java/blade/addon/features/highlight/WitherHighlight.java
+++ b/src/main/java/blade/addon/features/highlight/WitherHighlight.java
@@ -43,8 +43,8 @@ public class WitherHighlight {
 
         ClientTickEvents.END_CLIENT_TICK.register(client -> {withers.removeIf(wither -> wither.isRemoved() || wither.isDeadOrDying());});
 
-        RenderingEvents.FILLED_ENTITY.register(WitherHighlight::renderFilled);
-        RenderingEvents.OUTLINE_ENTITY.register(WitherHighlight::renderOutline);
+        RenderingEvents.FILLED.register(WitherHighlight::renderFilled);
+        RenderingEvents.LINE.register(WitherHighlight::renderOutline);
     }
 
     private static AABB getBox(WitherBoss wither) {
@@ -55,14 +55,14 @@ public class WitherHighlight {
         if (!MobHighlight.mobHighlight || !MobHighlight.renderFilled() || MobHighlight.dontRenderHighlight) return;
 
         float[] rgba = RenderUtils.toFloats(MobHighlight.witherFilledColor);
-        withers.forEach(entity -> RenderUtils.renderFilled(getBox(entity), rgba));
+        withers.forEach(entity -> RenderUtils.renderFilledBox(matrixStack, consumer, getBox(entity), rgba));
     }
 
     private static void renderOutline(WorldRenderContext context, PoseStack matrixStack, VertexConsumer consumer) {
         if (!MobHighlight.mobHighlight || !MobHighlight.renderOutline() || MobHighlight.dontRenderHighlight) return;
 
         float[] rgba = RenderUtils.toFloats(MobHighlight.witherOutlineColor);
-        withers.forEach(entity -> RenderUtils.renderOutline(getBox(entity), rgba));
+        withers.forEach(entity -> RenderUtils.renderOutlinedBox(matrixStack, consumer,getBox(entity), rgba));
     }
 
 }
diff --git a/src/main/java/blade/addon/features/other/PracticeSS.java b/src/main/java/blade/addon/features/other/PracticeSS.java
index 0c50d5c..867b2c1 100644
--- a/src/main/java/blade/addon/features/other/PracticeSS.java
+++ b/src/main/java/blade/addon/features/other/PracticeSS.java
@@ -111,7 +111,7 @@ public class PracticeSS {
             }
         });
 
-        RenderingEvents.FILLED_BLOCK.register(PracticeSS::render);
+        RenderingEvents.FILLED.register(PracticeSS::render);
         Events.ON_LOCATION_CHANGE.register(location -> {
             reset();
             return false;
@@ -352,15 +352,15 @@ public class PracticeSS {
         if (showingPattern) {
             if (!ExtraOptions.realisticDelay || totalTicks - ticksLeft > START_WAIT_DURATION || endIndex != 2) {
                 int lastIndex = getLastDisplayIndex();
-                renderButtons(0, lastIndex);
-                renderBackground(lastIndex - 1);
+                renderButtons(matrixStack, consumer, 0, lastIndex);
+                renderBackground(matrixStack, consumer, lastIndex - 1);
             }
         } else {
-            renderButtons(currentIndex, endIndex);
+            renderButtons(matrixStack, consumer, currentIndex, endIndex);
         }
     }
 
-    private static void renderButtons(int start, int end) {
+    private static void renderButtons(PoseStack matrixStack, VertexConsumer consumer, int start, int end) {
         AABB directionBox = getBox(direction);
         if (directionBox == null) return;
 
@@ -369,7 +369,7 @@ public class PracticeSS {
             BlockPos pos = buttons.get(i);
             AABB box = directionBox.move(pos.getX(), pos.getY(), pos.getZ());
             float[] color = RenderUtils.toFloats(getColor(i));
-            RenderUtils.renderFilled(box, color);
+            RenderUtils.renderFilledBox(matrixStack, consumer, box, color);
         }
     }
 
@@ -383,7 +383,7 @@ public class PracticeSS {
         };
     }
 
-    private static void renderBackground(int backgroundIndex) {
+    private static void renderBackground(PoseStack matrixStack, VertexConsumer consumer, int backgroundIndex) {
         if (buttons.size() > backgroundIndex && backgroundIndex >= 0) {
             AABB box = AABB.ofSize(buttons.get(backgroundIndex).getCenter(), 1, 1, 1);
             int dx, dz;
@@ -408,7 +408,7 @@ public class PracticeSS {
                     return;
                 }
             }
-            RenderUtils.renderFilled(box.move(dx, 0, dz), new float[]{0, 0.5f, 1, 1});
+            RenderUtils.renderFilledBox(matrixStack, consumer, box.move(dx, 0, dz), new float[]{0, 0.5f, 1, 1});
         }
     }
 }
diff --git a/src/main/java/blade/addon/utils/rendering/RenderHandler.java b/src/main/java/blade/addon/utils/rendering/RenderHandler.java
index 92fd645..bc78b74 100644
--- a/src/main/java/blade/addon/utils/rendering/RenderHandler.java
+++ b/src/main/java/blade/addon/utils/rendering/RenderHandler.java
@@ -1,11 +1,25 @@
 package blade.addon.utils.rendering;
 
+import com.mojang.blaze3d.vertex.PoseStack;
+import com.mojang.blaze3d.vertex.VertexConsumer;
+import net.fabricmc.fabric.api.client.rendering.v1.world.WorldRenderContext;
+import net.minecraft.client.renderer.MultiBufferSource;
+import net.minecraft.client.renderer.rendertype.RenderType;
+import net.minecraft.client.renderer.state.LevelRenderState;
+import net.minecraft.world.phys.Vec3;
+
 import java.util.ArrayList;
 import java.util.List;
 import java.util.function.Consumer;
 
 public class RenderHandler {
 
+    private final RenderType renderType;
+
+    public RenderHandler(RenderType renderType)  {
+        this.renderType = renderType;
+    }
+
     private final List<RenderingEvent> listeners = new ArrayList<>();
 
     public void register(RenderingEvent listener) {
@@ -19,4 +33,21 @@ public class RenderHandler {
         }
     }
 
+    public void init(WorldRenderContext context) {
+        LevelRenderState worldState = context.worldState();
+        if (worldState == null) return;
+        Vec3 camera = worldState.cameraRenderState.pos;
+        PoseStack matrices = context.matrices();
+        if (matrices == null) return;
+        matrices.pushPose();
+        matrices.translate(-camera.x, -camera.y, -camera.z);
+
+        MultiBufferSource consumers = context.consumers();
+        if (consumers == null) return;
+        VertexConsumer consumer = consumers.getBuffer(renderType);
+
+        invoke(renderingEvent -> renderingEvent.render(context, matrices, consumer));
+        matrices.popPose();
+    }
+
 }
diff --git a/src/main/java/blade/addon/utils/rendering/RenderLayers.java b/src/main/java/blade/addon/utils/rendering/RenderLayers.java
index 0988625..7d503d0 100644
--- a/src/main/java/blade/addon/utils/rendering/RenderLayers.java
+++ b/src/main/java/blade/addon/utils/rendering/RenderLayers.java
@@ -4,48 +4,23 @@ import net.minecraft.client.renderer.rendertype.LayeringTransform;
 import net.minecraft.client.renderer.rendertype.RenderSetup;
 import net.minecraft.client.renderer.rendertype.RenderType;
 
-import java.util.HashMap;
-
 public class RenderLayers {
 
-    private static final HashMap<Integer, RenderType> OUTLINE_WIDTHS = new HashMap<>();
-    private static final HashMap<Integer, RenderType> OUTLINE_WIDTHS_NO_DEPTH = new HashMap<>();
-
-    public static final RenderType FILLED_LAYER =
-            RenderType.create("filled", RenderSetup.builder(RenderPipelines.FILLED_PIPELINE).setLayeringTransform(LayeringTransform.VIEW_OFFSET_Z_LAYERING).createRenderSetup());
-
-
-    public static final RenderType FILLED_LAYER_NO_DEPTH =
-            RenderType.create("through_wall_filled", RenderSetup.builder(RenderPipelines.FILLED_PIPELINE_NO_DEPTH).setLayeringTransform(LayeringTransform.VIEW_OFFSET_Z_LAYERING).createRenderSetup());
-
-    public static final RenderType FILLED_ENTITY_LAYER =
-            RenderType.create("filled-entity", RenderSetup.builder(RenderPipelines.FILLED_ENTITY_PIPELINE).setLayeringTransform(LayeringTransform.VIEW_OFFSET_Z_LAYERING).createRenderSetup());
-
-    public static RenderType getOutline(int width, boolean depthCheck) {
-        if (depthCheck) {
-            if (OUTLINE_WIDTHS.containsKey(width)) {
-                return OUTLINE_WIDTHS.get(width);
-            }
-
-            RenderType outline_layer =
-                    RenderType.create("outline-entity-" + width, RenderSetup.builder(RenderPipelines.OUTLINE_ENTITY_PIPELINE).setOutline(RenderSetup.OutlineProperty.IS_OUTLINE).setLayeringTransform(LayeringTransform.VIEW_OFFSET_Z_LAYERING).createRenderSetup());
+    public static final RenderType FILLED_DEBUG =
+            RenderType.create("filled-debug", RenderSetup.builder(RenderPipelines.FILLED_DEBUG).createRenderSetup());
 
-            OUTLINE_WIDTHS.put(width, outline_layer);
-            return outline_layer;
-        } else {
-            if (OUTLINE_WIDTHS_NO_DEPTH.containsKey(width)) {
-                return OUTLINE_WIDTHS_NO_DEPTH.get(width);
-            }
+    public static final RenderType FILLED =
+            RenderType.create("filled", RenderSetup.builder(RenderPipelines.FILLED).createRenderSetup());
 
-            RenderType outline_layer =
-                    RenderType.create("outline-entity-" + width, RenderSetup.builder(RenderPipelines.OUTLINE_ENTITY_PIPELINE).setOutline(RenderSetup.OutlineProperty.IS_OUTLINE).setLayeringTransform(LayeringTransform.VIEW_OFFSET_Z_LAYERING).createRenderSetup());
+    public static final RenderType LINE =
+            RenderType.create("line", RenderSetup.builder(RenderPipelines.LINE).setOutline(RenderSetup.OutlineProperty.IS_OUTLINE).setLayeringTransform(LayeringTransform.VIEW_OFFSET_Z_LAYERING).createRenderSetup());
 
 
-            OUTLINE_WIDTHS_NO_DEPTH.put(width, outline_layer);
-            return outline_layer;
-        }
+    public static final RenderType FILLED_NO_DEPTH =
+            RenderType.create("filled-no-depth", RenderSetup.builder(RenderPipelines.FILLED_NO_DEPTH).createRenderSetup());
 
-    }
+    public static final RenderType LINE_NO_DEPTH =
+            RenderType.create("line-no-depth", RenderSetup.builder(RenderPipelines.LINE_NO_DEPTH).setOutline(RenderSetup.OutlineProperty.IS_OUTLINE).setLayeringTransform(LayeringTransform.VIEW_OFFSET_Z_LAYERING).createRenderSetup());
 
 
 }
diff --git a/src/main/java/blade/addon/utils/rendering/RenderPipelines.java b/src/main/java/blade/addon/utils/rendering/RenderPipelines.java
index 5db182a..217e07a 100644
--- a/src/main/java/blade/addon/utils/rendering/RenderPipelines.java
+++ b/src/main/java/blade/addon/utils/rendering/RenderPipelines.java
@@ -8,31 +8,36 @@ import com.mojang.blaze3d.vertex.VertexFormat;
 import net.minecraft.resources.Identifier;
 
 public class RenderPipelines {
-    public static final RenderPipeline FILLED_PIPELINE = net.minecraft.client.renderer.RenderPipelines.register(RenderPipeline.builder(net.minecraft.client.renderer.RenderPipelines.DEBUG_FILLED_SNIPPET)
-            .withLocation(Identifier.fromNamespaceAndPath(Constants.NAMESPACE, "filled"))
-            .withVertexFormat(DefaultVertexFormat.POSITION_COLOR, VertexFormat.Mode.TRIANGLE_STRIP)
+    public static final RenderPipeline FILLED_DEBUG = net.minecraft.client.renderer.RenderPipelines.register(RenderPipeline.builder(net.minecraft.client.renderer.RenderPipelines.DEBUG_FILLED_SNIPPET)
+            .withLocation(Identifier.fromNamespaceAndPath(Constants.NAMESPACE, "filled-debug"))
+            .withVertexFormat(DefaultVertexFormat.POSITION_COLOR, VertexFormat.Mode.QUADS)
             .build());
 
-    public static final RenderPipeline FILLED_PIPELINE_NO_DEPTH = net.minecraft.client.renderer.RenderPipelines.register(RenderPipeline.builder(net.minecraft.client.renderer.RenderPipelines.DEBUG_FILLED_SNIPPET)
-            .withLocation(Identifier.fromNamespaceAndPath(Constants.NAMESPACE, "through-walls-filled"))
-            .withVertexFormat(DefaultVertexFormat.POSITION_COLOR, VertexFormat.Mode.TRIANGLE_STRIP)
-            .withDepthTestFunction(DepthTestFunction.NO_DEPTH_TEST)
-            .build());
 
 
-    public static final RenderPipeline FILLED_ENTITY_PIPELINE = net.minecraft.client.renderer.RenderPipelines.register(RenderPipeline.builder(net.minecraft.client.renderer.RenderPipelines.DEBUG_FILLED_SNIPPET)
-            .withLocation(Identifier.fromNamespaceAndPath(Constants.NAMESPACE, "filled-entity"))
-            .withVertexFormat(DefaultVertexFormat.POSITION_COLOR, VertexFormat.Mode.TRIANGLE_STRIP)
+    public static final RenderPipeline FILLED = net.minecraft.client.renderer.RenderPipelines.register(RenderPipeline.builder(net.minecraft.client.renderer.RenderPipelines.DEBUG_FILLED_SNIPPET)
+            .withLocation(Identifier.fromNamespaceAndPath(Constants.NAMESPACE, "filled"))
+            .withVertexFormat(DefaultVertexFormat.POSITION_COLOR, VertexFormat.Mode.QUADS)
             .withDepthWrite(true)
             .build());
 
-    public static final RenderPipeline OUTLINE_ENTITY_PIPELINE = net.minecraft.client.renderer.RenderPipelines.register(RenderPipeline.builder(net.minecraft.client.renderer.RenderPipelines.LINES_SNIPPET)
-            .withLocation(Identifier.fromNamespaceAndPath(Constants.NAMESPACE, "outline-entity"))
+    public static final RenderPipeline LINE = net.minecraft.client.renderer.RenderPipelines.register(RenderPipeline.builder(net.minecraft.client.renderer.RenderPipelines.LINES_SNIPPET)
+            .withLocation(Identifier.fromNamespaceAndPath(Constants.NAMESPACE, "line"))
+            .withVertexFormat(DefaultVertexFormat.POSITION_COLOR_NORMAL_LINE_WIDTH, VertexFormat.Mode.LINES)
             .withDepthWrite(true)
             .build());
 
-    public static final RenderPipeline OUTLINE_ENTITY_PIPELINE_NO_DEPTH = net.minecraft.client.renderer.RenderPipelines.register(RenderPipeline.builder(net.minecraft.client.renderer.RenderPipelines.LINES_SNIPPET)
-            .withLocation(Identifier.fromNamespaceAndPath(Constants.NAMESPACE, "outline-entity-no-depth"))
+
+    public static final RenderPipeline FILLED_NO_DEPTH = net.minecraft.client.renderer.RenderPipelines.register(RenderPipeline.builder(net.minecraft.client.renderer.RenderPipelines.DEBUG_FILLED_SNIPPET)
+            .withLocation(Identifier.fromNamespaceAndPath(Constants.NAMESPACE, "filled-no-depth"))
+            .withVertexFormat(DefaultVertexFormat.POSITION_COLOR, VertexFormat.Mode.QUADS)
+            .withDepthTestFunction(DepthTestFunction.NO_DEPTH_TEST)
+            .build());
+
+
+    public static final RenderPipeline LINE_NO_DEPTH = net.minecraft.client.renderer.RenderPipelines.register(RenderPipeline.builder(net.minecraft.client.renderer.RenderPipelines.LINES_SNIPPET)
+            .withLocation(Identifier.fromNamespaceAndPath(blade.addon.utils.Constants.NAMESPACE, "line-no-depth"))
+            .withVertexFormat(DefaultVertexFormat.POSITION_COLOR_NORMAL_LINE_WIDTH, VertexFormat.Mode.LINES)
             .withDepthTestFunction(DepthTestFunction.NO_DEPTH_TEST)
             .build());
 
diff --git a/src/main/java/blade/addon/utils/rendering/RenderUtils.java b/src/main/java/blade/addon/utils/rendering/RenderUtils.java
index 36f4fda..24325e6 100644
--- a/src/main/java/blade/addon/utils/rendering/RenderUtils.java
+++ b/src/main/java/blade/addon/utils/rendering/RenderUtils.java
@@ -1,22 +1,23 @@
 package blade.addon.utils.rendering;
 
+import blade.addon.features.highlight.MobHighlight;
 import blade.addon.utils.Constants;
 import blade.addon.utils.config.values.ExtraOptions;
 import blade.addon.utils.config.values.Floor7;
 import blade.addon.utils.data.EntityUtil;
 import com.mojang.blaze3d.vertex.PoseStack;
+import com.mojang.blaze3d.vertex.VertexConsumer;
 import config.practical.hud.HUDComponent;
 import net.fabricmc.fabric.api.client.rendering.v1.world.WorldRenderContext;
 import net.minecraft.client.Minecraft;
 import net.minecraft.client.gui.Font;
 import net.minecraft.client.gui.GuiGraphics;
 import net.minecraft.client.player.LocalPlayer;
-import net.minecraft.gizmos.GizmoStyle;
 import net.minecraft.gizmos.Gizmos;
 import net.minecraft.network.chat.Component;
-import net.minecraft.util.ARGB;
 import net.minecraft.world.phys.AABB;
 import net.minecraft.world.phys.Vec3;
+import org.joml.Matrix4f;
 import org.joml.Vector3f;
 
 public class RenderUtils {
@@ -88,19 +89,6 @@ public class RenderUtils {
         context.drawString(textRenderer, drawnText, component.getScaledX(), component.getScaledY(), 0xffffffff, true);
     }
 
-    public static void renderFilled(AABB box, float[] rgba) {
-        if (rgba[3] == 0) return;
-        Gizmos.cuboid(box, GizmoStyle.fill(ARGB.colorFromFloat(rgba[3], rgba[0], rgba[1], rgba[2]))); // Could be filledAndStroked
-        //VertexRendering.drawFilledBox(matrixStack, consumer, box.minX, box.minY, box.minZ, box.maxX, box.maxY, box.maxZ, rgba[0], rgba[1], rgba[2], rgba[3]);
-    }
-
-    public static void renderOutline(AABB box, float[] rgba) {
-        if (rgba[3] == 0) return;
-        Gizmos.cuboid(box, GizmoStyle.stroke(ARGB.colorFromFloat(rgba[3], rgba[0], rgba[1], rgba[2]))); // Could be filledAndStroked
-        //VertexRendering.drawBox(matrixStack.peek(), consumer, box, rgba[0], rgba[1], rgba[2], rgba[3]);
-    }
-
-
     public static void renderText(WorldRenderContext context, PoseStack matrices, Component text, double x, double y, double z, float scale) {
         Minecraft client = Minecraft.getInstance();
         Font textRenderer = client.font;
@@ -137,7 +125,7 @@ public class RenderUtils {
         renderLineTo(context, pos.x, pos.y, pos.z, color);
     }
 
-        public static String formatNumber(float num) {
+    public static String formatNumber(float num) {
         if (Floor7.capitalizeHealthNumbers) {
             if (num >= 1e9) return String.format("%.1fB", num / 1e9);
             if (num >= 1e6) return String.format("%.1fM", num / 1e6);
@@ -147,6 +135,142 @@ public class RenderUtils {
             if (num >= 1e6) return String.format("%.1fm", num / 1e6);
             if (num >= 1e3) return String.format("%.1fk", num / 1e3);
         }
-            return num + "";
-        }
+        return num + "";
+    }
+
+    private static void quad(Matrix4f matrix,
+                             VertexConsumer consumer,
+                             double x1, double y1, double z1,
+                             double x2, double y2, double z2,
+                             double x3, double y3, double z3,
+                             double x4, double y4, double z4,
+
+                             float r, float g, float b, float a) {
+
+        consumer.addVertex(matrix, (float) x1, (float) y1, (float) z1)
+                .setColor(r, g, b, a)
+                .setNormal(0, 1, 0);
+
+        consumer.addVertex(matrix, (float) x2, (float) y2, (float) z2)
+                .setColor(r, g, b, a)
+                .setNormal(0, 1, 0);
+
+        consumer.addVertex(matrix, (float) x3, (float) y3, (float) z3)
+                .setColor(r, g, b, a)
+                .setNormal(0, 1, 0);
+
+        consumer.addVertex(matrix, (float) x4, (float) y4, (float) z4)
+                .setColor(r, g, b, a)
+                .setNormal(0, 1, 0);
+
+    }
+
+    private static void verticalLine(Matrix4f matrix,
+                                     VertexConsumer consumer,
+                                     double x1, double y1, double z1,
+                                     double x2, double y2, double z2,
+                                     float r, float g, float b, float a, float width) {
+
+        consumer.addVertex(matrix, (float) x1, (float) y1, (float) z1)
+                .setColor(r, g, b, a)
+                .setNormal(1, 0, 0).setLineWidth(width);
+
+        consumer.addVertex(matrix, (float) x2, (float) y2, (float) z2)
+                .setColor(r, g, b, a)
+                .setNormal(1, 0, 0).setLineWidth(width);
+
+        consumer.addVertex(matrix, (float) x1, (float) y1, (float) z1)
+                .setColor(r, g, b, a)
+                .setNormal(0, 0, 1).setLineWidth(width);
+
+        consumer.addVertex(matrix, (float) x2, (float) y2, (float) z2)
+                .setColor(r, g, b, a)
+                .setNormal(0, 0, 1).setLineWidth(width);
+    }
+
+    private static void horizontalLine(Matrix4f matrix,
+                                       VertexConsumer consumer,
+                                       double x1, double y1, double z1,
+                                       double x2, double y2, double z2,
+                                       float r, float g, float b, float a, float width) {
+
+        consumer.addVertex(matrix, (float) x1, (float) y1, (float) z1)
+                .setColor(r, g, b, a)
+                .setNormal(0, 1, 0).setLineWidth(width);
+
+        consumer.addVertex(matrix, (float) x2, (float) y2, (float) z2)
+                .setColor(r, g, b, a)
+                .setNormal(0, 1, 0).setLineWidth(width);
+    }
+
+    public static void renderOutlinedBox(PoseStack matrices,
+                                       VertexConsumer consumer,
+                                       AABB box,
+                                       float[] rgba) {
+
+
+        if (rgba[3] == 0) return;
+        float r = rgba[0];
+        float g = rgba[1];
+        float b = rgba[2];
+        float a = rgba[3];
+
+        Matrix4f matrix = matrices.last().pose();
+
+        double minX = box.minX;
+        double minY = box.minY;
+        double minZ = box.minZ;
+        double maxX = box.maxX;
+        double maxY = box.maxY;
+        double maxZ = box.maxZ;
+
+        horizontalLine(matrix, consumer, minX, minY, minZ, minX, maxY, minZ, r, g, b, a, MobHighlight.outlineWidth);
+        horizontalLine(matrix, consumer, maxX, minY, minZ, maxX, maxY, minZ, r, g, b, a, MobHighlight.outlineWidth);
+        horizontalLine(matrix, consumer, minX, minY, maxZ, minX, maxY, maxZ, r, g, b, a, MobHighlight.outlineWidth);
+        horizontalLine(matrix, consumer, maxX, minY, maxZ, maxX, maxY, maxZ, r, g, b, a, MobHighlight.outlineWidth);
+
+        verticalLine(matrix, consumer, minX, maxY, minZ, maxX, maxY, minZ, r, g, b, a, MobHighlight.outlineWidth);
+        verticalLine(matrix, consumer, maxX, maxY, minZ, maxX, maxY, maxZ, r, g, b, a, MobHighlight.outlineWidth);
+        verticalLine(matrix, consumer, maxX, maxY, maxZ, minX, maxY, maxZ, r, g, b, a, MobHighlight.outlineWidth);
+        verticalLine(matrix, consumer, minX, maxY, maxZ, minX, maxY, minZ, r, g, b, a, MobHighlight.outlineWidth);
+
+        verticalLine(matrix, consumer, minX, minY, minZ, maxX, minY, minZ, r, g, b, a, MobHighlight.outlineWidth);
+        verticalLine(matrix, consumer, maxX, minY, minZ, maxX, minY, maxZ, r, g, b, a, MobHighlight.outlineWidth);
+        verticalLine(matrix, consumer, maxX, minY, maxZ, minX, minY, maxZ, r, g, b, a, MobHighlight.outlineWidth);
+        verticalLine(matrix, consumer, minX, minY, maxZ, minX, minY, minZ, r, g, b, a, MobHighlight.outlineWidth);
+
+
+    }
+
+    public static void renderFilledBox(PoseStack matrices,
+                                     VertexConsumer consumer,
+                                     AABB box,
+                                     float[] rgba) {
+
+        if (rgba[3] == 0) return;
+        float r = rgba[0];
+        float g = rgba[1];
+        float b = rgba[2];
+        float a = rgba[3];
+
+        Matrix4f matrix = matrices.last().pose();
+
+        double minX = box.minX;
+        double minY = box.minY;
+        double minZ = box.minZ;
+        double maxX = box.maxX;
+        double maxY = box.maxY;
+        double maxZ = box.maxZ;
+
+        quad(matrix, consumer, minX, minY, minZ, minX, maxY, minZ, maxX, maxY, minZ, maxX, minY, minZ, r, g, b, a);
+        quad(matrix, consumer, maxX, minY, minZ, maxX, maxY, minZ, maxX, maxY, maxZ, maxX, minY, maxZ, r, g, b, a);
+
+        quad(matrix, consumer, maxX, minY, maxZ, maxX, maxY, maxZ, minX, maxY, maxZ, minX, minY, maxZ, r, g, b, a);
+        quad(matrix, consumer, minX, minY, maxZ, minX, maxY, maxZ, minX, maxY, minZ, minX, minY, minZ, r, g, b, a);
+
+        quad(matrix, consumer, minX, maxY, minZ, minX, maxY, maxZ, maxX, maxY, maxZ, maxX, maxY, minZ, r, g, b, a);
+        quad(matrix, consumer, maxX, minY, minZ, maxX, minY, maxZ, minX, minY, maxZ, minX, minY, minZ, r, g, b, a);
+
+    }
+
 }
diff --git a/src/main/java/blade/addon/utils/rendering/RenderingEvents.java b/src/main/java/blade/addon/utils/rendering/RenderingEvents.java
index 9366ea7..0e48b9d 100644
--- a/src/main/java/blade/addon/utils/rendering/RenderingEvents.java
+++ b/src/main/java/blade/addon/utils/rendering/RenderingEvents.java
@@ -1,135 +1,21 @@
 package blade.addon.utils.rendering;
 
-import blade.addon.features.highlight.MobHighlight;
-import com.mojang.blaze3d.vertex.PoseStack;
-import com.mojang.blaze3d.vertex.VertexConsumer;
-import net.fabricmc.fabric.api.client.rendering.v1.world.WorldRenderContext;
 import net.fabricmc.fabric.api.client.rendering.v1.world.WorldRenderEvents;
-import net.minecraft.client.renderer.MultiBufferSource;
-import net.minecraft.client.renderer.state.LevelRenderState;
-import net.minecraft.world.phys.Vec3;
 
 public class RenderingEvents {
 
-    public static RenderHandler FILLED_BLOCK = new RenderHandler();
-    public static RenderHandler NO_DEPTH_FILLED = new RenderHandler();
-    public static RenderHandler FILLED_ENTITY = new RenderHandler();
-    public static RenderHandler OUTLINE_ENTITY = new RenderHandler();
-    public static RenderHandler NO_DEPTH_OUTLINE_ENTITY = new RenderHandler();
-    public static RenderHandler LINE = new RenderHandler();
+    public static RenderHandler FILLED_DEBUG = new RenderHandler(RenderLayers.FILLED_DEBUG);
+    public static RenderHandler FILLED = new RenderHandler(RenderLayers.FILLED);
+    public static RenderHandler LINE = new RenderHandler(RenderLayers.LINE);
+    public static RenderHandler FILLED_NO_DEPTH = new RenderHandler(RenderLayers.FILLED_NO_DEPTH);
+    public static RenderHandler LINE_NO_DEPTH = new RenderHandler(RenderLayers.LINE_NO_DEPTH);
 
 
     public static void init() {
-        WorldRenderEvents.BEFORE_DEBUG_RENDER.register(RenderingEvents::filled);
-        WorldRenderEvents.BEFORE_DEBUG_RENDER.register(RenderingEvents::filledNoDepth);
-        WorldRenderEvents.AFTER_ENTITIES.register(RenderingEvents::entityFilled);
-        WorldRenderEvents.AFTER_ENTITIES.register(RenderingEvents::entityOutline);
-        WorldRenderEvents.AFTER_ENTITIES.register(RenderingEvents::entityOutlineNoDepth);
-        WorldRenderEvents.BEFORE_DEBUG_RENDER.register(RenderingEvents::debugLine);
-
-    }
-
-    private static void filled(WorldRenderContext context) {
-        LevelRenderState worldState = context.worldState();
-        if (worldState == null) return;
-        Vec3 camera = worldState.cameraRenderState.pos;
-        PoseStack matrices = context.matrices();
-        if (matrices == null) return;
-        matrices.pushPose();
-        matrices.translate(-camera.x, -camera.y, -camera.z);
-
-        MultiBufferSource consumers = context.consumers();
-        if (consumers == null) return;
-        VertexConsumer consumer = consumers.getBuffer(RenderLayers.FILLED_LAYER);
-
-        FILLED_BLOCK.invoke(renderingEvent -> renderingEvent.render(context, matrices, consumer));
-        matrices.popPose();
-    }
-
-    private static void filledNoDepth(WorldRenderContext context) {
-        LevelRenderState worldState = context.worldState();
-        if (worldState == null) return;
-        Vec3 camera = worldState.cameraRenderState.pos;
-        PoseStack matrices = context.matrices();
-        if (matrices == null) return;
-        matrices.pushPose();
-        matrices.translate(-camera.x, -camera.y, -camera.z);
-
-        MultiBufferSource consumers = context.consumers();
-        if (consumers == null) return;
-        VertexConsumer consumer = consumers.getBuffer(RenderLayers.FILLED_LAYER_NO_DEPTH);
-
-        NO_DEPTH_FILLED.invoke(renderingEvent -> renderingEvent.render(context, matrices, consumer));
-        matrices.popPose();
+        WorldRenderEvents.BEFORE_DEBUG_RENDER.register(FILLED_DEBUG::init);
+        WorldRenderEvents.AFTER_ENTITIES.register(FILLED::init);
+        WorldRenderEvents.AFTER_ENTITIES.register(LINE::init);
+        WorldRenderEvents.AFTER_ENTITIES.register(FILLED_NO_DEPTH::init);
+        WorldRenderEvents.AFTER_ENTITIES.register(LINE_NO_DEPTH::init);
     }
-
-    private static void entityFilled(WorldRenderContext context) {
-        LevelRenderState worldState = context.worldState();
-        if (worldState == null) return;
-        Vec3 camera = worldState.cameraRenderState.pos;
-        PoseStack matrices = context.matrices();
-        if (matrices == null) return;
-        matrices.pushPose();
-        matrices.translate(-camera.x, -camera.y, -camera.z);
-
-        MultiBufferSource consumers = context.consumers();
-        if (consumers == null) return;
-        VertexConsumer consumer = consumers.getBuffer(RenderLayers.FILLED_ENTITY_LAYER);
-
-        FILLED_ENTITY.invoke(renderingEvent -> renderingEvent.render(context, matrices, consumer));
-        matrices.popPose();
-    }
-
-
-    private static void entityOutline(WorldRenderContext context) {
-        LevelRenderState worldState = context.worldState();
-        if (worldState == null) return;
-        Vec3 camera = worldState.cameraRenderState.pos;
-        PoseStack matrices = context.matrices();
-        if (matrices == null) return;
-        matrices.pushPose();
-        matrices.translate(-camera.x, -camera.y, -camera.z);
-
-        MultiBufferSource consumers = context.consumers();
-        if (consumers == null) return;
-        VertexConsumer consumer = consumers.getBuffer(RenderLayers.getOutline(MobHighlight.outlineWidth, true));
-
-        OUTLINE_ENTITY.invoke(renderingEvent -> renderingEvent.render(context, matrices, consumer));
-        matrices.popPose();
-    }
-
-    private static void entityOutlineNoDepth(WorldRenderContext context) {
-        LevelRenderState worldState = context.worldState();
-        if (worldState == null) return;
-        Vec3 camera = worldState.cameraRenderState.pos;
-        PoseStack matrices = context.matrices();
-        if (matrices == null) return;
-        matrices.pushPose();
-        matrices.translate(-camera.x, -camera.y, -camera.z);
-
-        MultiBufferSource consumers = context.consumers();
-        if (consumers == null) return;
-        VertexConsumer consumer = consumers.getBuffer(RenderLayers.getOutline(MobHighlight.outlineWidth, false));
-
-        NO_DEPTH_OUTLINE_ENTITY.invoke(renderingEvent -> renderingEvent.render(context, matrices, consumer));
-        matrices.popPose();
-    }
-
-    private static void debugLine(WorldRenderContext context) {
-        LevelRenderState worldState = context.worldState();
-        if (worldState == null) return;
-        Vec3 camera = context.worldState().cameraRenderState.pos;
-        PoseStack matrices = context.matrices();
-        if (matrices == null) return;
-        matrices.pushPose();
-        matrices.translate(-camera.x, -camera.y, -camera.z);
-
-        MultiBufferSource consumers = context.consumers();
-        if (consumers == null) return;
-        VertexConsumer consumer = consumers.getBuffer(RenderLayers.getOutline(4, true));
-
-        LINE.invoke(renderingEvent -> renderingEvent.render(context, matrices, consumer));
-        matrices.popPose();
-    }
-
 }
- swapped drawcontext variable name to graphicsGabriel Ellnebrand · 2026-05-17 · 1725947
查看变更
src/main/java/blade/addon/features/dungeon/BloodNotifier.java                          |  4 ++--
 src/main/java/blade/addon/features/dungeon/ChestCounter.java                           |  6 +++---
 src/main/java/blade/addon/features/dungeon/QuizTimer.java                              |  6 +++---
 src/main/java/blade/addon/features/dungeon/RunStartValidator.java                      |  8 ++++----
 src/main/java/blade/addon/features/dungeon/WarpCooldown.java                           |  4 ++--
 src/main/java/blade/addon/features/dungeon/f7/RelicTimer.java                          |  8 ++++----
 src/main/java/blade/addon/features/dungeon/f7/dragons/DragSpawnTimer.java              |  4 ++--
 src/main/java/blade/addon/features/dungeon/f7/invincibility/InvincibilityDuration.java |  4 ++--
 src/main/java/blade/addon/features/dungeon/f7/invincibility/InvincibilityTimer.java    | 18 +++++++++---------
 src/main/java/blade/addon/features/dungeon/f7/location/LocationNotifier.java           |  4 ++--
 src/main/java/blade/addon/features/dungeon/f7/maxor/CrystalSpawn.java                  |  8 ++++----
 src/main/java/blade/addon/features/dungeon/f7/maxor/MaxorStun.java                     |  4 ++--
 src/main/java/blade/addon/features/dungeon/f7/maxor/MaxorTickTimer.java                |  4 ++--
 src/main/java/blade/addon/features/dungeon/f7/storm/DistanceToLedge.java               |  4 ++--
 src/main/java/blade/addon/features/dungeon/f7/storm/PillarExplode.java                 |  8 ++++----
 src/main/java/blade/addon/features/dungeon/f7/storm/StormTickTimer.java                |  8 ++++----
 src/main/java/blade/addon/features/dungeon/f7/terms/CurrentSection.java                |  4 ++--
 src/main/java/blade/addon/features/dungeon/f7/terms/DeviceNotifier.java                |  4 ++--
 src/main/java/blade/addon/features/dungeon/f7/terms/GoldorTickTimer.java               |  4 ++--
 src/main/java/blade/addon/features/dungeon/f7/terms/LeapNotification.java              |  4 ++--
 src/main/java/blade/addon/features/dungeon/f7/terms/MelodyWarning.java                 |  4 ++--
 src/main/java/blade/addon/features/dungeon/f7/terms/SectionCompletion.java             |  4 ++--
 src/main/java/blade/addon/features/dungeon/f7/terms/SectionProgress.java               |  6 +++---
 src/main/java/blade/addon/features/dungeon/f7/terms/TermStartTimer.java                |  4 ++--
 src/main/java/blade/addon/features/item/HeldItemToolTip.java                           |  4 ++--
 src/main/java/blade/addon/features/notifications/Notifications.java                    |  4 ++--
 src/main/java/blade/addon/features/other/ArrowSwapper.java                             |  8 ++++----
 src/main/java/blade/addon/features/other/KickedTimer.java                              |  4 ++--
 src/main/java/blade/addon/features/other/RagDisplay.java                               |  4 ++--
 src/main/java/blade/addon/features/other/ReaperDisplay.java                            |  4 ++--
 src/main/java/blade/addon/features/other/SelectedPet.java                              |  4 ++--
 src/main/java/blade/addon/utils/config/components/CombinedScreenNotifications.java     | 26 +++++++++++++-------------
 src/main/java/blade/addon/utils/config/components/CombinedTickTimer.java               | 14 +++++++-------
 33 files changed, 104 insertions(+), 104 deletions(-)
diff --git a/src/main/java/blade/addon/features/dungeon/BloodNotifier.java b/src/main/java/blade/addon/features/dungeon/BloodNotifier.java
index dc4a9f4..902fed4 100644
--- a/src/main/java/blade/addon/features/dungeon/BloodNotifier.java
+++ b/src/main/java/blade/addon/features/dungeon/BloodNotifier.java
@@ -53,8 +53,8 @@ public class BloodNotifier {
         return Dungeons.alertBloodSpawns && System.currentTimeMillis() - prevTime < 2000 && correctClass();
     }
 
-    public static void render(HUDComponent component, GuiGraphics context) {
-        RenderUtils.drawCenteredText(context, component, Component.literal("First four mobs spawned!"), Constants.RED);
+    public static void render(HUDComponent component, GuiGraphics graphics) {
+        RenderUtils.drawCenteredText(graphics, component, Component.literal("First four mobs spawned!"), Constants.RED);
     }
 
 }
diff --git a/src/main/java/blade/addon/features/dungeon/ChestCounter.java b/src/main/java/blade/addon/features/dungeon/ChestCounter.java
index 99564e5..3151e0e 100644
--- a/src/main/java/blade/addon/features/dungeon/ChestCounter.java
+++ b/src/main/java/blade/addon/features/dungeon/ChestCounter.java
@@ -66,14 +66,14 @@ public class ChestCounter {
         return Dungeons.displayChestCount && Location.inDungeon();
     }
 
-    public static void render(HUDComponent component, GuiGraphics context) {
+    public static void render(HUDComponent component, GuiGraphics graphics) {
         int x = component.getScaledX();
         int y = component.getScaledY();
 
         if (hasUpdatedData) {
-            RenderUtils.drawPrefixedText(component, context, "Chests", Math.min(chestDisplayCount + countedChests, 60) + "");
+            RenderUtils.drawPrefixedText(component, graphics, "Chests", Math.min(chestDisplayCount + countedChests, 60) + "");
         } else {
-            context.drawString(Minecraft.getInstance().font, Component.literal("go to the dungeon hub"), x, y, Constants.RED, true);
+            graphics.drawString(Minecraft.getInstance().font, Component.literal("go to the dungeon hub"), x, y, Constants.RED, true);
 
         }
     }
diff --git a/src/main/java/blade/addon/features/dungeon/QuizTimer.java b/src/main/java/blade/addon/features/dungeon/QuizTimer.java
index f694db3..1377a9d 100644
--- a/src/main/java/blade/addon/features/dungeon/QuizTimer.java
+++ b/src/main/java/blade/addon/features/dungeon/QuizTimer.java
@@ -56,7 +56,7 @@ public class QuizTimer {
         return Dungeons.quizTimer && tick > 0;
     }
 
-    public static void render(HUDComponent component, GuiGraphics context) {
+    public static void render(HUDComponent component, GuiGraphics graphics) {
         Font textRenderer = Minecraft.getInstance().font;
         if (textRenderer == null) return;
 
@@ -65,9 +65,9 @@ public class QuizTimer {
                     .append(Component.literal(stage + "/3").withColor(0xffffffff)
                             .append(Component.literal("): ").withColor(ExtraOptions.timerPrefixColor))
                             .append(Component.literal(Constants.DECIMAL_FORMAT.format(tick * Constants.TICK_DURATION) + "s").withColor(0xffffffff)));
-            context.drawString(textRenderer, drawnText, component.getScaledX(), component.getScaledY(), 0xffffffff, true);
+            graphics.drawString(textRenderer, drawnText, component.getScaledX(), component.getScaledY(), 0xffffffff, true);
         } else {
-            RenderUtils.drawPrefixedTimer(component, context, "Quiz", tick);
+            RenderUtils.drawPrefixedTimer(component, graphics, "Quiz", tick);
         }
     }
 
diff --git a/src/main/java/blade/addon/features/dungeon/RunStartValidator.java b/src/main/java/blade/addon/features/dungeon/RunStartValidator.java
index 9fb0815..fbdd203 100644
--- a/src/main/java/blade/addon/features/dungeon/RunStartValidator.java
+++ b/src/main/java/blade/addon/features/dungeon/RunStartValidator.java
@@ -134,13 +134,13 @@ public class RunStartValidator {
         return Location.inDungeon() && !Phase.runStarted() && (hasDupeClasses || notEnoughPlayers);
     }
 
-    public static void render(HUDComponent component, GuiGraphics context) {
+    public static void render(HUDComponent component, GuiGraphics graphics) {
         if (hasDupeClasses) {
-            RenderUtils.drawCenteredText(context, component, DUPE_CLASS_TEXT);
+            RenderUtils.drawCenteredText(graphics, component, DUPE_CLASS_TEXT);
         } else if (notEnoughPlayers) {
-            RenderUtils.drawCenteredText(context, component, PLAYER_COUNT_TEXT);
+            RenderUtils.drawCenteredText(graphics, component, PLAYER_COUNT_TEXT);
         } else {
-            RenderUtils.drawCenteredText(context, component, Component.literal("§cSome warning text"));
+            RenderUtils.drawCenteredText(graphics, component, Component.literal("§cSome warning text"));
         }
     }
 }
diff --git a/src/main/java/blade/addon/features/dungeon/WarpCooldown.java b/src/main/java/blade/addon/features/dungeon/WarpCooldown.java
index 92a9623..e6b497d 100644
--- a/src/main/java/blade/addon/features/dungeon/WarpCooldown.java
+++ b/src/main/java/blade/addon/features/dungeon/WarpCooldown.java
@@ -42,8 +42,8 @@ public class WarpCooldown {
         }
     }
 
-    public static void render(HUDComponent component, GuiGraphics context) {
+    public static void render(HUDComponent component, GuiGraphics graphics) {
         double timeRemaining = Math.max((endTime - System.currentTimeMillis()) / 1000.0, 0);
-        RenderUtils.drawPrefixedTimer(component, context, "Warp Cooldown", timeRemaining);
+        RenderUtils.drawPrefixedTimer(component, graphics, "Warp Cooldown", timeRemaining);
     }
 }
diff --git a/src/main/java/blade/addon/features/dungeon/f7/RelicTimer.java b/src/main/java/blade/addon/features/dungeon/f7/RelicTimer.java
index 8c983e2..daa107b 100644
--- a/src/main/java/blade/addon/features/dungeon/f7/RelicTimer.java
+++ b/src/main/java/blade/addon/features/dungeon/f7/RelicTimer.java
@@ -271,9 +271,9 @@ public class RelicTimer {
         return Floor7.enableRelicStartTimer && Location.inDungeon() && Phase.inP5() && tick > -1 && !Floor7.replaceWithProgressBar;
     }
 
-    public static void render(HUDComponent component, GuiGraphics context) {
+    public static void render(HUDComponent component, GuiGraphics graphics) {
         int color = tick > 7 ? GREEN_COLOR : RED_COLOR;
-        RenderUtils.drawTimer(component, context, tick, color);
+        RenderUtils.drawTimer(component, graphics, tick, color);
     }
 
     public static boolean displayProgressBar() {
@@ -281,7 +281,7 @@ public class RelicTimer {
         return Floor7.enableRelicStartTimer && Location.inDungeon() && Phase.inP5() && tick > -1 && Floor7.replaceWithProgressBar;
     }
 
-    public static void renderProgressBar(HUDComponent component, GuiGraphics context) {
+    public static void renderProgressBar(HUDComponent component, GuiGraphics graphics) {
         int x = component.getScaledX();
         int y = component.getScaledY();
 
@@ -307,7 +307,7 @@ public class RelicTimer {
             message.append("§8]");
         }
 
-        RenderUtils.drawCenteredText(context, Minecraft.getInstance().font, Component.literal(message.toString()), x, y, component.getWidth(), 0xffffffff);
+        RenderUtils.drawCenteredText(graphics, Minecraft.getInstance().font, Component.literal(message.toString()), x, y, component.getWidth(), 0xffffffff);
 
     }
 }
diff --git a/src/main/java/blade/addon/features/dungeon/f7/dragons/DragSpawnTimer.java b/src/main/java/blade/addon/features/dungeon/f7/dragons/DragSpawnTimer.java
index b5c2327..594acd2 100644
--- a/src/main/java/blade/addon/features/dungeon/f7/dragons/DragSpawnTimer.java
+++ b/src/main/java/blade/addon/features/dungeon/f7/dragons/DragSpawnTimer.java
@@ -99,7 +99,7 @@ public class DragSpawnTimer {
         return Floor7.dragSpawnTimers && currentDragon != Dragon.NONE;
     }
 
-    public static void render(HUDComponent component, GuiGraphics context) {
-        RenderUtils.drawTimer(component, context, tick, currentDragon.color);
+    public static void render(HUDComponent component, GuiGraphics graphics) {
+        RenderUtils.drawTimer(component, graphics, tick, currentDragon.color);
     }
 }
diff --git a/src/main/java/blade/addon/features/dungeon/f7/invincibility/InvincibilityDuration.java b/src/main/java/blade/addon/features/dungeon/f7/invincibility/InvincibilityDuration.java
index ceefdcb..0634731 100644
--- a/src/main/java/blade/addon/features/dungeon/f7/invincibility/InvincibilityDuration.java
+++ b/src/main/java/blade/addon/features/dungeon/f7/invincibility/InvincibilityDuration.java
@@ -26,11 +26,11 @@ public class InvincibilityDuration {
         return  ticks > 0 && Dungeons.InvincibilityDuration;
     }
 
-    public static void render(HUDComponent component, GuiGraphics context) {
+    public static void render(HUDComponent component, GuiGraphics graphics) {
         int color = 0xffffffff;
         if (Dungeons.useStatusColorForInvincibility) {
             color = RenderUtils.getStatusColor(40, 20, ticks);
         }
-        RenderUtils.drawTimer(component, context, ticks, color);
+        RenderUtils.drawTimer(component, graphics, ticks, color);
     }
 }
diff --git a/src/main/java/blade/addon/features/dungeon/f7/invincibility/InvincibilityTimer.java b/src/main/java/blade/addon/features/dungeon/f7/invincibility/InvincibilityTimer.java
index 9b79984..3b24c1a 100644
--- a/src/main/java/blade/addon/features/dungeon/f7/invincibility/InvincibilityTimer.java
+++ b/src/main/java/blade/addon/features/dungeon/f7/invincibility/InvincibilityTimer.java
@@ -190,23 +190,23 @@ public class InvincibilityTimer {
         }
     }
 
-    public static void render(HUDComponent component, GuiGraphics context) {
+    public static void render(HUDComponent component, GuiGraphics graphics) {
         int x = component.getScaledX();
         int y = component.getScaledY();
 
         if (Dungeons.useSprites) {
-            drawSprite(context, BONZO_SPRITE, x, y, bonzoMaskOn, bonzoMaskTicks, formatTimer(bonzoMaskTicks));
-            drawSprite(context, SPIRIT_SPRITE, x, y + SPRITE_SIZE + 1, spiritMaskOn, spiritMaskTicks, formatTimer(spiritMaskTicks));
-            drawSprite(context, PHOENIX_SPRITE, x, y + (SPRITE_SIZE + 1) * 2, phoenixOn, phoenixTicks, formatTimer(phoenixTicks));
+            drawSprite(graphics, BONZO_SPRITE, x, y, bonzoMaskOn, bonzoMaskTicks, formatTimer(bonzoMaskTicks));
+            drawSprite(graphics, SPIRIT_SPRITE, x, y + SPRITE_SIZE + 1, spiritMaskOn, spiritMaskTicks, formatTimer(spiritMaskTicks));
+            drawSprite(graphics, PHOENIX_SPRITE, x, y + (SPRITE_SIZE + 1) * 2, phoenixOn, phoenixTicks, formatTimer(phoenixTicks));
         } else {
             if (Dungeons.removeMaskPart) {
-                context.drawString(Minecraft.getInstance().font, getText(bonzoMaskTicks, bonzoMaskOn, "Bonzo "), x, y, 0xffffffff, true);
-                context.drawString(Minecraft.getInstance().font, getText(spiritMaskTicks, spiritMaskOn, "Spirit "), x, y + TEXT_HEIGHT, 0xffffffff, true);
+                graphics.drawString(Minecraft.getInstance().font, getText(bonzoMaskTicks, bonzoMaskOn, "Bonzo "), x, y, 0xffffffff, true);
+                graphics.drawString(Minecraft.getInstance().font, getText(spiritMaskTicks, spiritMaskOn, "Spirit "), x, y + TEXT_HEIGHT, 0xffffffff, true);
             } else {
-                context.drawString(Minecraft.getInstance().font, getText(bonzoMaskTicks, bonzoMaskOn, "Bonzo's Mask "), x, y, 0xffffffff, true);
-                context.drawString(Minecraft.getInstance().font, getText(spiritMaskTicks, spiritMaskOn, "Spirit Mask "), x, y + TEXT_HEIGHT, 0xffffffff, true);
+                graphics.drawString(Minecraft.getInstance().font, getText(bonzoMaskTicks, bonzoMaskOn, "Bonzo's Mask "), x, y, 0xffffffff, true);
+                graphics.drawString(Minecraft.getInstance().font, getText(spiritMaskTicks, spiritMaskOn, "Spirit Mask "), x, y + TEXT_HEIGHT, 0xffffffff, true);
             }
-            context.drawString(Minecraft.getInstance().font, getText(phoenixTicks, phoenixOn, "Phoenix "), x, y + TEXT_HEIGHT * 2, 0xffffffff, true);
+            graphics.drawString(Minecraft.getInstance().font, getText(phoenixTicks, phoenixOn, "Phoenix "), x, y + TEXT_HEIGHT * 2, 0xffffffff, true);
         }
     }
 }
diff --git a/src/main/java/blade/addon/features/dungeon/f7/location/LocationNotifier.java b/src/main/java/blade/addon/features/dungeon/f7/location/LocationNotifier.java
index 006e42e..b52faa8 100644
--- a/src/main/java/blade/addon/features/dungeon/f7/location/LocationNotifier.java
+++ b/src/main/java/blade/addon/features/dungeon/f7/location/LocationNotifier.java
@@ -65,7 +65,7 @@ public class LocationNotifier {
         return ticks > 0 && Floor7.displayLocationNotification;
     }
 
-    public static void render(HUDComponent component, GuiGraphics context) {
-        RenderUtils.drawCenteredText(context, component, notification);
+    public static void render(HUDComponent component, GuiGraphics graphics) {
+        RenderUtils.drawCenteredText(graphics, component, notification);
     }
 }
diff --git a/src/main/java/blade/addon/features/dungeon/f7/maxor/CrystalSpawn.java b/src/main/java/blade/addon/features/dungeon/f7/maxor/CrystalSpawn.java
index 1ea93e9..980fbe3 100644
--- a/src/main/java/blade/addon/features/dungeon/f7/maxor/CrystalSpawn.java
+++ b/src/main/java/blade/addon/features/dungeon/f7/maxor/CrystalSpawn.java
@@ -98,16 +98,16 @@ public class CrystalSpawn {
         return tick > 0 && Location.inDungeon() && Phase.inP1() && Floor7.enableCrystalSpawnTime;
     }
 
-    public static void render(HUDComponent component, GuiGraphics context) {
-        RenderUtils.drawTimer(component, context, tick, Constants.LIGHT_PURPLE);
+    public static void render(HUDComponent component, GuiGraphics graphics) {
+        RenderUtils.drawTimer(component, graphics, tick, Constants.LIGHT_PURPLE);
     }
 
     public static boolean displayNotification() {
         return (Floor7.instantlyDisplayCrystalReminder || tickSincePicked > REMINDER_TICK) && Location.inDungeon() && Phase.inP1() && Floor7.crystalPlaceReminder && pickedUp;
     }
 
-    public static void renderNotification(HUDComponent component, GuiGraphics context) {
-        RenderUtils.drawCenteredText(context, component, Component.literal("§bPlace Crystal!"));
+    public static void renderNotification(HUDComponent component, GuiGraphics graphics) {
+        RenderUtils.drawCenteredText(graphics, component, Component.literal("§bPlace Crystal!"));
     }
 
 
diff --git a/src/main/java/blade/addon/features/dungeon/f7/maxor/MaxorStun.java b/src/main/java/blade/addon/features/dungeon/f7/maxor/MaxorStun.java
index 34f3d11..8b60e3d 100644
--- a/src/main/java/blade/addon/features/dungeon/f7/maxor/MaxorStun.java
+++ b/src/main/java/blade/addon/features/dungeon/f7/maxor/MaxorStun.java
@@ -51,8 +51,8 @@ public class MaxorStun {
         return tick > 0 && Location.inDungeon() && Phase.inP1() && Floor7.maxorStunDuration && stunned;
     }
 
-    public static void render(HUDComponent component, GuiGraphics context) {
-        RenderUtils.drawPrefixedText(component, context, "Stunned", TextUtil.formatTicks(tick));
+    public static void render(HUDComponent component, GuiGraphics graphics) {
+        RenderUtils.drawPrefixedText(component, graphics, "Stunned", TextUtil.formatTicks(tick));
     }
 
 
diff --git a/src/main/java/blade/addon/features/dungeon/f7/maxor/MaxorTickTimer.java b/src/main/java/blade/addon/features/dungeon/f7/maxor/MaxorTickTimer.java
index e8eff78..eebfec1 100644
--- a/src/main/java/blade/addon/features/dungeon/f7/maxor/MaxorTickTimer.java
+++ b/src/main/java/blade/addon/features/dungeon/f7/maxor/MaxorTickTimer.java
@@ -28,8 +28,8 @@ public class MaxorTickTimer {
         return Floor7.enableMaxorTickTimer && Location.inDungeon() && Phase.inP1();
     }
 
-    public static void render(HUDComponent component, GuiGraphics context) {
-        RenderUtils.drawTimer(component, context, tick, 0xffffffff);
+    public static void render(HUDComponent component, GuiGraphics graphics) {
+        RenderUtils.drawTimer(component, graphics, tick, 0xffffffff);
     }
 
 }
diff --git a/src/main/java/blade/addon/features/dungeon/f7/storm/DistanceToLedge.java b/src/main/java/blade/addon/features/dungeon/f7/storm/DistanceToLedge.java
index 8c50ba3..0026c1a 100644
--- a/src/main/java/blade/addon/features/dungeon/f7/storm/DistanceToLedge.java
+++ b/src/main/java/blade/addon/features/dungeon/f7/storm/DistanceToLedge.java
@@ -39,7 +39,7 @@ public class DistanceToLedge {
         return Phase.inP2() && !Phase.stormDead() && (DungeonClass.isClass(DungeonClass.MAGE) || DungeonClass.isClass(DungeonClass.ARCHER));
     }
 
-    public static void render(HUDComponent component, GuiGraphics context) {
-        RenderUtils.drawTimer(component, context, getDistance(), 0xffffffff);
+    public static void render(HUDComponent component, GuiGraphics graphics) {
+        RenderUtils.drawTimer(component, graphics, getDistance(), 0xffffffff);
     }
 }
diff --git a/src/main/java/blade/addon/features/dungeon/f7/storm/PillarExplode.java b/src/main/java/blade/addon/features/dungeon/f7/storm/PillarExplode.java
index f7ce306..4aa47c1 100644
--- a/src/main/java/blade/addon/features/dungeon/f7/storm/PillarExplode.java
+++ b/src/main/java/blade/addon/features/dungeon/f7/storm/PillarExplode.java
@@ -43,16 +43,16 @@ public class PillarExplode {
         return Floor7.timePillarExplosion && tick > 0;
     }
 
-    public static void renderTimer(HUDComponent component, GuiGraphics context) {
+    public static void renderTimer(HUDComponent component, GuiGraphics graphics) {
         int color = tick < 6 ? Constants.GREEN : Constants.RED;
-        RenderUtils.drawTimer(component, context, tick, color);
+        RenderUtils.drawTimer(component, graphics, tick, color);
     }
 
     public static boolean display() {
         return Floor7.notifyStormCrush && tick > 0;
     }
 
-    public static void render(HUDComponent component, GuiGraphics context) {
-        RenderUtils.drawCenteredText(context, component, Component.literal("§6||| §bStorm crushed! §6|||"));
+    public static void render(HUDComponent component, GuiGraphics graphics) {
+        RenderUtils.drawCenteredText(graphics, component, Component.literal("§6||| §bStorm crushed! §6|||"));
     }
 }
diff --git a/src/main/java/blade/addon/features/dungeon/f7/storm/StormTickTimer.java b/src/main/java/blade/addon/features/dungeon/f7/storm/StormTickTimer.java
index 14d1c9d..963a4d0 100644
--- a/src/main/java/blade/addon/features/dungeon/f7/storm/StormTickTimer.java
+++ b/src/main/java/blade/addon/features/dungeon/f7/storm/StormTickTimer.java
@@ -72,19 +72,19 @@ public class StormTickTimer {
         return Floor7.enableStormTickTimer && Location.inDungeon() && Phase.inP2() && !Phase.stormDead();
     }
 
-    public static void render(HUDComponent component, GuiGraphics context) {
+    public static void render(HUDComponent component, GuiGraphics graphics) {
         double num = tick * Constants.TICK_DURATION;
         if (Floor7.tickDownStormTickTimer) {
             num = CRUSH_TICK * Constants.TICK_DURATION - num;
         }
-        RenderUtils.drawTimer(component, context, num, Floor7.stormTickTimerColor);
+        RenderUtils.drawTimer(component, graphics, num, Floor7.stormTickTimerColor);
     }
 
     public static boolean displayDeathTime() {
         return Floor7.enableStormDeathTime && Location.inDungeon() && Phase.inP2() && !Phase.stormDead() && deathTime > 0 && deathStartDisplayTime > System.currentTimeMillis() - DEATH_DISPLAY_DURATION;
     }
 
-    public static void renderDeathTime(HUDComponent component, GuiGraphics context) {
-        RenderUtils.drawTimer(component, context, deathTime, Constants.DARK_PURPLE);
+    public static void renderDeathTime(HUDComponent component, GuiGraphics graphics) {
+        RenderUtils.drawTimer(component, graphics, deathTime, Constants.DARK_PURPLE);
     }
 }
diff --git a/src/main/java/blade/addon/features/dungeon/f7/terms/CurrentSection.java b/src/main/java/blade/addon/features/dungeon/f7/terms/CurrentSection.java
index f1821aa..0f5c86d 100644
--- a/src/main/java/blade/addon/features/dungeon/f7/terms/CurrentSection.java
+++ b/src/main/java/blade/addon/features/dungeon/f7/terms/CurrentSection.java
@@ -26,8 +26,8 @@ public class CurrentSection {
         return Floor7.showCurrentSection && Phase.inTerminals();
     }
 
-    public static void render(HUDComponent component, GuiGraphics context) {
-        RenderUtils.drawPrefixedText(component, context, "Section", " " + section);
+    public static void render(HUDComponent component, GuiGraphics graphics) {
+        RenderUtils.drawPrefixedText(component, graphics, "Section", " " + section);
     }
 
 }
diff --git a/src/main/java/blade/addon/features/dungeon/f7/terms/DeviceNotifier.java b/src/main/java/blade/addon/features/dungeon/f7/terms/DeviceNotifier.java
index 9c26b62..bf14599 100644
--- a/src/main/java/blade/addon/features/dungeon/f7/terms/DeviceNotifier.java
+++ b/src/main/java/blade/addon/features/dungeon/f7/terms/DeviceNotifier.java
@@ -74,8 +74,8 @@ public class DeviceNotifier {
         return showNotification;
     }
 
-    public static void render(HUDComponent component, GuiGraphics context) {
+    public static void render(HUDComponent component, GuiGraphics graphics) {
         if (System.currentTimeMillis() - completedTime >= TOTAL_DURATION) showNotification = false;
-        RenderUtils.drawCenteredText(context, component, Component.literal("§aDevice Completed!"));
+        RenderUtils.drawCenteredText(graphics, component, Component.literal("§aDevice Completed!"));
     }
 }
diff --git a/src/main/java/blade/addon/features/dungeon/f7/terms/GoldorTickTimer.java b/src/main/java/blade/addon/features/dungeon/f7/terms/GoldorTickTimer.java
index 1eeac40..2daa728 100644
--- a/src/main/java/blade/addon/features/dungeon/f7/terms/GoldorTickTimer.java
+++ b/src/main/java/blade/addon/features/dungeon/f7/terms/GoldorTickTimer.java
@@ -31,7 +31,7 @@ public class GoldorTickTimer {
         return Floor7.enableGoldorTickTimer && Location.inDungeon() && Phase.inTerminals();
     }
 
-    public static void render(HUDComponent component, GuiGraphics context) {
+    public static void render(HUDComponent component, GuiGraphics graphics) {
         double num = tick * Constants.TICK_DURATION;
         double mod = num % 3;
         if (Floor7.inDeathTicks && !Floor7.makeGoldorTickUp) mod = 3.0 - mod;
@@ -39,6 +39,6 @@ public class GoldorTickTimer {
 
         int color = (mod < 1 ? Constants.GREEN : mod < 2 ? Constants.GOLD : Constants.RED);
 
-        RenderUtils.drawTimer(component, context, num, color);
+        RenderUtils.drawTimer(component, graphics, num, color);
     }
 }
diff --git a/src/main/java/blade/addon/features/dungeon/f7/terms/LeapNotification.java b/src/main/java/blade/addon/features/dungeon/f7/terms/LeapNotification.java
index 555f775..754840a 100644
--- a/src/main/java/blade/addon/features/dungeon/f7/terms/LeapNotification.java
+++ b/src/main/java/blade/addon/features/dungeon/f7/terms/LeapNotification.java
@@ -100,12 +100,12 @@ public class LeapNotification {
         return Floor7.leapNotifications && inBounds && Phase.inBoss() && Location.inDungeon();
     }
 
-    public static void render(HUDComponent component, GuiGraphics context) {
+    public static void render(HUDComponent component, GuiGraphics graphics) {
         int maxCount = getMaxCount();
 
         String startFormat;
         startFormat = (maxCount - count <= 1? "§9" : "§4");
 
-        RenderUtils.drawCenteredText(context, component, Component.literal(startFormat + count + "§9/" + maxCount + " Players Leaped"));
+        RenderUtils.drawCenteredText(graphics, component, Component.literal(startFormat + count + "§9/" + maxCount + " Players Leaped"));
     }
 }
diff --git a/src/main/java/blade/addon/features/dungeon/f7/terms/MelodyWarning.java b/src/main/java/blade/addon/features/dungeon/f7/terms/MelodyWarning.java
index 774e0d2..0e7d439 100644
--- a/src/main/java/blade/addon/features/dungeon/f7/terms/MelodyWarning.java
+++ b/src/main/java/blade/addon/features/dungeon/f7/terms/MelodyWarning.java
@@ -80,7 +80,7 @@ public class MelodyWarning {
         return melodyStarted && Floor7.notifiyMelody && !ownUsername;
     }
 
-    public static void render(HUDComponent component, GuiGraphics context) {
+    public static void render(HUDComponent component, GuiGraphics graphics) {
         DungeonClass dungeonClass = DungeonClass.getClass(name);
         int num = Math.min(furthestProgress / 25, 3);
 
@@ -94,7 +94,7 @@ public class MelodyWarning {
 
         Component text = nameText.append(infoText);
 
-        RenderUtils.drawCenteredText(context, component, text);
+        RenderUtils.drawCenteredText(graphics, component, text);
     }
 
 }
diff --git a/src/main/java/blade/addon/features/dungeon/f7/terms/SectionCompletion.java b/src/main/java/blade/addon/features/dungeon/f7/terms/SectionCompletion.java
index d765d2f..9ee7509 100644
--- a/src/main/java/blade/addon/features/dungeon/f7/terms/SectionCompletion.java
+++ b/src/main/java/blade/addon/features/dungeon/f7/terms/SectionCompletion.java
@@ -25,8 +25,8 @@ public class SectionCompletion {
         return System.currentTimeMillis() - prevTime < 1000 && Floor7.sectionCompletionNotification;
     }
 
-    public static void render(HUDComponent component, GuiGraphics context) {
-        RenderUtils.drawCenteredText(context, component, Component.literal("Section completed!").withStyle(ChatFormatting.GREEN));
+    public static void render(HUDComponent component, GuiGraphics graphics) {
+        RenderUtils.drawCenteredText(graphics, component, Component.literal("Section completed!").withStyle(ChatFormatting.GREEN));
     }
 
 }
diff --git a/src/main/java/blade/addon/features/dungeon/f7/terms/SectionProgress.java b/src/main/java/blade/addon/features/dungeon/f7/terms/SectionProgress.java
index f71a77b..d81c9f4 100644
--- a/src/main/java/blade/addon/features/dungeon/f7/terms/SectionProgress.java
+++ b/src/main/java/blade/addon/features/dungeon/f7/terms/SectionProgress.java
@@ -100,11 +100,11 @@ public class SectionProgress {
         return Floor7.showSectionProgress && Phase.inTerminals();
     }
 
-    public static void render(HUDComponent component, GuiGraphics context) {
+    public static void render(HUDComponent component, GuiGraphics graphics) {
         if (Floor7.sectionPrevObjective) {
-            RenderUtils.drawCenteredText(context, component, Component.literal(objectiveFormat + prevObjective + " ").append(getProgressText()));
+            RenderUtils.drawCenteredText(graphics, component, Component.literal(objectiveFormat + prevObjective + " ").append(getProgressText()));
         } else {
-            RenderUtils.drawCenteredText(context, component, getProgressText());
+            RenderUtils.drawCenteredText(graphics, component, getProgressText());
         }
 
     }
diff --git a/src/main/java/blade/addon/features/dungeon/f7/terms/TermStartTimer.java b/src/main/java/blade/addon/features/dungeon/f7/terms/TermStartTimer.java
index 66d7762..45862f2 100644
--- a/src/main/java/blade/addon/features/dungeon/f7/terms/TermStartTimer.java
+++ b/src/main/java/blade/addon/features/dungeon/f7/terms/TermStartTimer.java
@@ -32,8 +32,8 @@ public class TermStartTimer {
         return Floor7.enableTermStartTimer && Location.inDungeon() && Phase.inP2() && Phase.stormDead();
     }
 
-    public static void render(HUDComponent component, GuiGraphics context) {
+    public static void render(HUDComponent component, GuiGraphics graphics) {
         double num = tick * Constants.TICK_DURATION;
-        RenderUtils.drawCenteredText(context, component, Component.literal(Constants.DECIMAL_FORMAT.format(num)), Constants.YELLOW);
+        RenderUtils.drawCenteredText(graphics, component, Component.literal(Constants.DECIMAL_FORMAT.format(num)), Constants.YELLOW);
     }
 }
diff --git a/src/main/java/blade/addon/features/item/HeldItemToolTip.java b/src/main/java/blade/addon/features/item/HeldItemToolTip.java
index ae65444..89d89e2 100644
--- a/src/main/java/blade/addon/features/item/HeldItemToolTip.java
+++ b/src/main/java/blade/addon/features/item/HeldItemToolTip.java
@@ -29,9 +29,9 @@ public class HeldItemToolTip {
         return ExtraOptions.moveToolTip && heldItemFade > 0;
     }
 
-    public static void render(HUDComponent component, GuiGraphics context) {
+    public static void render(HUDComponent component, GuiGraphics graphics) {
         int currentColor = heldItemFade > 0? color: 0xffffffff;
-        RenderUtils.drawCenteredText(context, component, tooltip, currentColor);
+        RenderUtils.drawCenteredText(graphics, component, tooltip, currentColor);
     }
 
 }
diff --git a/src/main/java/blade/addon/features/notifications/Notifications.java b/src/main/java/blade/addon/features/notifications/Notifications.java
index 7f8e3aa..56b0d9a 100644
--- a/src/main/java/blade/addon/features/notifications/Notifications.java
+++ b/src/main/java/blade/addon/features/notifications/Notifications.java
@@ -77,9 +77,9 @@ public class Notifications {
         return tick > 0;
     }
 
-    public static void render(HUDComponent component, GuiGraphics context) {
+    public static void render(HUDComponent component, GuiGraphics graphics) {
         Component text = Component.literal(message != null ? message : "Some notification");
-        RenderUtils.drawCenteredText(context, component, text);
+        RenderUtils.drawCenteredText(graphics, component, text);
     }
 
     public static void setNotification(Notification notification) {
diff --git a/src/main/java/blade/addon/features/other/ArrowSwapper.java b/src/main/java/blade/addon/features/other/ArrowSwapper.java
index a5868eb..89e2a69 100644
--- a/src/main/java/blade/addon/features/other/ArrowSwapper.java
+++ b/src/main/java/blade/addon/features/other/ArrowSwapper.java
@@ -80,15 +80,15 @@ public class ArrowSwapper {
         return ExtraOptions.displayCurrentArrow;
     }
 
-    public static void render(HUDComponent component, GuiGraphics context) {
-        RenderUtils.drawText(context, component, displayedText, 0xffffffff);
+    public static void render(HUDComponent component, GuiGraphics graphics) {
+        RenderUtils.drawText(graphics, component, displayedText, 0xffffffff);
     }
 
     public static boolean displayNotification() {
         return ExtraOptions.arrowSwapNotification && tick > 0;
     }
 
-    public static void renderNotification(HUDComponent component, GuiGraphics context) {
-        RenderUtils.drawCenteredText(context, component, displayedText, 0xffffffff);
+    public static void renderNotification(HUDComponent component, GuiGraphics graphics) {
+        RenderUtils.drawCenteredText(graphics, component, displayedText, 0xffffffff);
     }
 }
diff --git a/src/main/java/blade/addon/features/other/KickedTimer.java b/src/main/java/blade/addon/features/other/KickedTimer.java
index 289f702..3ceeb18 100644
--- a/src/main/java/blade/addon/features/other/KickedTimer.java
+++ b/src/main/java/blade/addon/features/other/KickedTimer.java
@@ -30,11 +30,11 @@ public class KickedTimer {
         return isKicked;
     }
 
-    public static void render(HUDComponent component, GuiGraphics context) {
+    public static void render(HUDComponent component, GuiGraphics graphics) {
         long diff = System.currentTimeMillis() - kickedTime;
         if (diff > 60 * 1000) isKicked = false;
         double drawnTime = Math.min(diff / 1000.0, 60.0);
-        RenderUtils.drawPrefixedTimer(component, context, "Time kicked", drawnTime);
+        RenderUtils.drawPrefixedTimer(component, graphics, "Time kicked", drawnTime);
     }
 
 }
diff --git a/src/main/java/blade/addon/features/other/RagDisplay.java b/src/main/java/blade/addon/features/other/RagDisplay.java
index 68da2b3..be84311 100644
--- a/src/main/java/blade/addon/features/other/RagDisplay.java
+++ b/src/main/java/blade/addon/features/other/RagDisplay.java
@@ -53,8 +53,8 @@ public class RagDisplay {
         return tick > 0;
     }
 
-    public static void render(HUDComponent component, GuiGraphics context) {
-        RenderUtils.drawTimer(component, context, tick, Constants.YELLOW);
+    public static void render(HUDComponent component, GuiGraphics graphics) {
+        RenderUtils.drawTimer(component, graphics, tick, Constants.YELLOW);
     }
 
 }
diff --git a/src/main/java/blade/addon/features/other/ReaperDisplay.java b/src/main/java/blade/addon/features/other/ReaperDisplay.java
index 737ccbe..c97c181 100644
--- a/src/main/java/blade/addon/features/other/ReaperDisplay.java
+++ b/src/main/java/blade/addon/features/other/ReaperDisplay.java
@@ -34,7 +34,7 @@ public class ReaperDisplay {
         return tick > 0;
     }
 
-    public static void render(HUDComponent component, GuiGraphics context) {
-        RenderUtils.drawTimer(component, context, tick, Constants.RED);
+    public static void render(HUDComponent component, GuiGraphics graphics) {
+        RenderUtils.drawTimer(component, graphics, tick, Constants.RED);
     }
 }
diff --git a/src/main/java/blade/addon/features/other/SelectedPet.java b/src/main/java/blade/addon/features/other/SelectedPet.java
index a13c3e5..8d36056 100644
--- a/src/main/java/blade/addon/features/other/SelectedPet.java
+++ b/src/main/java/blade/addon/features/other/SelectedPet.java
@@ -191,8 +191,8 @@ public class SelectedPet {
         return ExtraOptions.sendPetSwapNotification && tick > 0;
     }
 
-    public static void renderNotification(HUDComponent component, GuiGraphics context) {
-        RenderUtils.drawCenteredText(context, component, currentPetText);
+    public static void renderNotification(HUDComponent component, GuiGraphics graphics) {
+        RenderUtils.drawCenteredText(graphics, component, currentPetText);
     }
 
 }
diff --git a/src/main/java/blade/addon/utils/config/components/CombinedScreenNotifications.java b/src/main/java/blade/addon/utils/config/components/CombinedScreenNotifications.java
index bf8a0ff..7e47130 100644
--- a/src/main/java/blade/addon/utils/config/components/CombinedScreenNotifications.java
+++ b/src/main/java/blade/addon/utils/config/components/CombinedScreenNotifications.java
@@ -48,31 +48,31 @@ public class CombinedScreenNotifications {
         return false;
     }
 
-    public static void render(HUDComponent component, GuiGraphics context) {
+    public static void render(HUDComponent component, GuiGraphics graphics) {
         if (RunStartValidator.display()) {
-            RunStartValidator.render(component, context);
+            RunStartValidator.render(component, graphics);
         } else if (KeyNotifier.display()) {
-            KeyNotifier.render(component, context);
+            KeyNotifier.render(component, graphics);
         } else if (DeviceNotifier.display()) {
-            DeviceNotifier.render(component, context);
+            DeviceNotifier.render(component, graphics);
         } else if (MelodyWarning.display()) {
-            MelodyWarning.render(component, context);
+            MelodyWarning.render(component, graphics);
         } else if (PillarExplode.display()) {
-            PillarExplode.render(component, context);
+            PillarExplode.render(component, graphics);
         } else if (CrystalSpawn.displayNotification()) {
-            CrystalSpawn.renderNotification(component, context);
+            CrystalSpawn.renderNotification(component, graphics);
         }else if (Notifications.display()) {
-            Notifications.render(component, context);
+            Notifications.render(component, graphics);
         } else if (SelectedPet.displayNotification()) {
-            SelectedPet.renderNotification(component, context);
+            SelectedPet.renderNotification(component, graphics);
         }   else if (ArrowSwapper.displayNotification()) {
-            ArrowSwapper.renderNotification(component, context);
+            ArrowSwapper.renderNotification(component, graphics);
         }   else if (SectionCompletion.display()) {
-            SectionCompletion.render(component, context);
+            SectionCompletion.render(component, graphics);
         }else if (BloodNotifier.display()) {
-            BloodNotifier.render(component, context);
+            BloodNotifier.render(component, graphics);
         }else {
-            RenderUtils.drawCenteredText(context, component, Component.literal("Some notification"));
+            RenderUtils.drawCenteredText(graphics, component, Component.literal("Some notification"));
         }
 
     }
diff --git a/src/main/java/blade/addon/utils/config/components/CombinedTickTimer.java b/src/main/java/blade/addon/utils/config/components/CombinedTickTimer.java
index 2d2aca6..315197d 100644
--- a/src/main/java/blade/addon/utils/config/components/CombinedTickTimer.java
+++ b/src/main/java/blade/addon/utils/config/components/CombinedTickTimer.java
@@ -31,19 +31,19 @@ public class CombinedTickTimer {
         return false;
     }
 
-    public static void render(HUDComponent component, GuiGraphics context) {
+    public static void render(HUDComponent component, GuiGraphics graphics) {
         if (CrystalSpawn.display()) {
-            CrystalSpawn.render(component, context);
+            CrystalSpawn.render(component, graphics);
         } else if (StormTickTimer.display()) {
-            StormTickTimer.render(component, context);
+            StormTickTimer.render(component, graphics);
         } else if (TermStartTimer.display()) {
-          TermStartTimer.render(component, context);
+          TermStartTimer.render(component, graphics);
         } else if (GoldorTickTimer.display()) {
-            GoldorTickTimer.render(component, context);
+            GoldorTickTimer.render(component, graphics);
         } else if (RelicTimer.display()) {
-            RelicTimer.render(component, context);
+            RelicTimer.render(component, graphics);
         } else {
-            RenderUtils.drawCenteredText(context, component, Component.literal(Constants.DECIMAL_FORMAT.format(0.0)));
+            RenderUtils.drawCenteredText(graphics, component, Component.literal(Constants.DECIMAL_FORMAT.format(0.0)));
         }
 
     }
- migrated to mojang mappingsGabriel Ellnebrand · 2026-05-16 · 2f665ac
查看变更
build.gradle                                                                                        |   3 +-
 gradle.properties                                                                                   |   6 +-
 src/main/java/blade/addon/features/dungeon/AutoRequeue.java                                         |  11 +-
 src/main/java/blade/addon/features/dungeon/BloodNotifier.java                                       |  12 +-
 src/main/java/blade/addon/features/dungeon/ChestCounter.java                                        |  19 +-
 src/main/java/blade/addon/features/dungeon/ExplosiveShot.java                                       |   5 +-
 src/main/java/blade/addon/features/dungeon/HidePlayers.java                                         |  16 +-
 src/main/java/blade/addon/features/dungeon/ItemHighlight.java                                       |  54 +++---
 src/main/java/blade/addon/features/dungeon/KeyNotifier.java                                         |  45 +++--
 src/main/java/blade/addon/features/dungeon/LeapMessage.java                                         |   8 +-
 src/main/java/blade/addon/features/dungeon/LeapOrder.java                                           |  25 ++-
 src/main/java/blade/addon/features/dungeon/QuizTimer.java                                           |  22 +--
 src/main/java/blade/addon/features/dungeon/RunStartValidator.java                                   |  35 ++--
 src/main/java/blade/addon/features/dungeon/SecretSpawnTimer.java                                    |  11 +-
 src/main/java/blade/addon/features/dungeon/WarpCooldown.java                                        |   5 +-
 src/main/java/blade/addon/features/dungeon/f7/BossWaypoints.java                                    |  37 ++--
 src/main/java/blade/addon/features/dungeon/f7/PredevTimer.java                                      |   8 +-
 src/main/java/blade/addon/features/dungeon/f7/RelicTimer.java                                       |  94 +++++-----
 src/main/java/blade/addon/features/dungeon/f7/dragons/DragSpawnTimer.java                           |  28 +--
 src/main/java/blade/addon/features/dungeon/f7/dragons/Dragon.java                                   |  18 +-
 src/main/java/blade/addon/features/dungeon/f7/dragons/DragonHealth.java                             |  25 ++-
 src/main/java/blade/addon/features/dungeon/f7/dragons/DragonTracer.java                             |   6 +-
 src/main/java/blade/addon/features/dungeon/f7/invincibility/InvincibilityDuration.java              |   4 +-
 src/main/java/blade/addon/features/dungeon/f7/invincibility/InvincibilityTimer.java                 |  57 +++---
 src/main/java/blade/addon/features/dungeon/f7/invincibility/MaskHighlight.java                      |   6 +-
 src/main/java/blade/addon/features/dungeon/f7/location/LocationNotifier.java                        |  13 +-
 src/main/java/blade/addon/features/dungeon/f7/location/PositionMessage.java                         |  18 +-
 src/main/java/blade/addon/features/dungeon/f7/location/PositionMessages.java                        |  52 +++---
 src/main/java/blade/addon/features/dungeon/f7/maxor/CrystalSpawn.java                               |  23 ++-
 src/main/java/blade/addon/features/dungeon/f7/maxor/MaxorStun.java                                  |   4 +-
 src/main/java/blade/addon/features/dungeon/f7/maxor/MaxorTickTimer.java                             |   4 +-
 src/main/java/blade/addon/features/dungeon/f7/storm/DistanceToLedge.java                            |  18 +-
 src/main/java/blade/addon/features/dungeon/f7/storm/PillarExplode.java                              |  14 +-
 src/main/java/blade/addon/features/dungeon/f7/storm/StormTickTimer.java                             |  13 +-
 src/main/java/blade/addon/features/dungeon/f7/terms/CurrentSection.java                             |   4 +-
 src/main/java/blade/addon/features/dungeon/f7/terms/DeviceNotifier.java                             |  34 ++--
 src/main/java/blade/addon/features/dungeon/f7/terms/GoldorTickTimer.java                            |   4 +-
 src/main/java/blade/addon/features/dungeon/f7/terms/LeapNotification.java                           |  59 +++---
 src/main/java/blade/addon/features/dungeon/f7/terms/MelodyWarning.java                              |  17 +-
 src/main/java/blade/addon/features/dungeon/f7/terms/SectionCompletion.java                          |  10 +-
 src/main/java/blade/addon/features/dungeon/f7/terms/SectionProgress.java                            |  14 +-
 src/main/java/blade/addon/features/dungeon/f7/terms/TermStartTimer.java                             |   8 +-
 src/main/java/blade/addon/features/dungeon/f7/terms/TitleHider.java                                 |   5 +-
 src/main/java/blade/addon/features/filter/FilterEntry.java                                          |  89 +++++----
 src/main/java/blade/addon/features/filter/FilterList.java                                           |  62 +++----
 src/main/java/blade/addon/features/filter/Filters.java                                              |   5 +-
 src/main/java/blade/addon/features/highlight/BatHighlight.java                                      |  17 +-
 src/main/java/blade/addon/features/highlight/MimicHighlight.java                                    |  21 +--
 src/main/java/blade/addon/features/highlight/MobHighlight.java                                      | 101 +++++-----
 src/main/java/blade/addon/features/highlight/SheepHighlight.java                                    |  17 +-
 src/main/java/blade/addon/features/highlight/TeammateHighlight.java                                 |  25 ++-
 src/main/java/blade/addon/features/highlight/WitherHighlight.java                                   |  23 ++-
 src/main/java/blade/addon/features/item/DropAnimation.java                                          |   4 +-
 src/main/java/blade/addon/features/item/HeldItemToolTip.java                                        |  14 +-
 src/main/java/blade/addon/features/item/ItemRarityHighlight.java                                    |  31 ++--
 src/main/java/blade/addon/features/item/ProtectItem.java                                            |  63 ++++---
 src/main/java/blade/addon/features/item/SelectedPetHighlight.java                                   |  19 +-
 src/main/java/blade/addon/features/item/StarCountHighlight.java                                     |  28 +--
 src/main/java/blade/addon/features/notifications/Notification.java                                  |   9 +-
 src/main/java/blade/addon/features/notifications/NotificationEditScreen.java                        |  40 ++--
 src/main/java/blade/addon/features/notifications/NotificationEntry.java                             |  60 +++---
 src/main/java/blade/addon/features/notifications/NotificationList.java                              |  46 ++---
 src/main/java/blade/addon/features/notifications/Notifications.java                                 |  15 +-
 src/main/java/blade/addon/features/other/ArrowSwapper.java                                          |  31 ++--
 src/main/java/blade/addon/features/other/CompactHoppity.java                                        |   5 +-
 src/main/java/blade/addon/features/other/DianaNotifier.java                                         |  18 +-
 src/main/java/blade/addon/features/other/InventoryButton.java                                       |  25 ++-
 src/main/java/blade/addon/features/other/KickedTimer.java                                           |   4 +-
 src/main/java/blade/addon/features/other/PracticeSS.java                                            |  83 +++++----
 src/main/java/blade/addon/features/other/RagDisplay.java                                            |  16 +-
 src/main/java/blade/addon/features/other/ReaperDisplay.java                                         |   8 +-
 src/main/java/blade/addon/features/other/SearchBar.java                                             |  48 ++---
 src/main/java/blade/addon/features/other/SelectedPet.java                                           |  57 +++---
 src/main/java/blade/addon/features/other/SwingAnimation.java                                        |   2 +-
 src/main/java/blade/addon/features/sound/BonzoSound.java                                            |  10 +-
 src/main/java/blade/addon/features/sound/TubaSound.java                                             |  12 +-
 src/main/java/blade/addon/mixin/{StuckArrowsFeatureRendererMixin.java => ArrowLayerMixin.java}      |   8 +-
 src/main/java/blade/addon/mixin/{BossBarHudMixin.java => BossHealthOverlayMixin.java}               |  20 +-
 src/main/java/blade/addon/mixin/ChatComponentMixin.java                                             |  26 +++
 src/main/java/blade/addon/mixin/ChatHudAccessor.java                                                |  26 ---
 src/main/java/blade/addon/mixin/ChatScreenMixin.java                                                |  44 ++---
 src/main/java/blade/addon/mixin/ClientPacketListenerMixin.java                                      | 121 ++++++++++++
 src/main/java/blade/addon/mixin/ClientPlayNetworkHandlerMixin.java                                  | 121 ------------
 src/main/java/blade/addon/mixin/{ClientConnectionMixin.java => ConnectionMixin.java}                |  24 +--
 src/main/java/blade/addon/mixin/DrawContextMixin.java                                               |  50 -----
 src/main/java/blade/addon/mixin/EffectsInInventoryMixin.java                                        |  30 +++
 src/main/java/blade/addon/mixin/EnderPearlItemMixin.java                                            |  12 +-
 src/main/java/blade/addon/mixin/EntityMixin.java                                                    |   4 +-
 src/main/java/blade/addon/mixin/{EntityRenderManagerMixin.java => EntityRenderDispatcherMixin.java} |  42 ++---
 src/main/java/blade/addon/mixin/EntityRendererMixin.java                                            |  28 +--
 src/main/java/blade/addon/mixin/GuiGraphicsMixin.java                                               |  50 +++++
 src/main/java/blade/addon/mixin/GuiMixin.java                                                       |  96 ++++++++++
 src/main/java/blade/addon/mixin/HandledScreenMixin.java                                             |  60 +++---
 src/main/java/blade/addon/mixin/HeldItemRendererMixin.java                                          |  44 ++---
 src/main/java/blade/addon/mixin/InGameHudMixin.java                                                 |  96 ----------
 src/main/java/blade/addon/mixin/{PlayerInventoryMixin.java => InventoryMixin.java}                  |  10 +-
 src/main/java/blade/addon/mixin/InventoryScreenMixin.java                                           |  36 ++--
 src/main/java/blade/addon/mixin/ItemStackMixin.java                                                 |   2 +-
 src/main/java/blade/addon/mixin/KeyboardHandlerMixin.java                                           |  26 +++
 src/main/java/blade/addon/mixin/KeyboardMixin.java                                                  |  26 ---
 src/main/java/blade/addon/mixin/{ChunkMixin.java => LevelChunkMixin.java}                           |   8 +-
 src/main/java/blade/addon/mixin/LivingEntityMixin.java                                              |  10 +-
 src/main/java/blade/addon/mixin/{ClientPlayerEntityMixin.java => LocalPlayerMixin.java}             |  18 +-
 src/main/java/blade/addon/mixin/{MinecraftClientMixin.java => MinecraftMixin.java}                  |  26 +--
 src/main/java/blade/addon/mixin/{MouseMixin.java => MouseHandlerMixin.java}                         |   8 +-
 src/main/java/blade/addon/mixin/{RecipeBookWidgetMixin.java => RecipeBookComponentMixin.java}       |  14 +-
 src/main/java/blade/addon/mixin/RecipeBookScreenMixin.java                                          |   6 +-
 src/main/java/blade/addon/mixin/ShovelItemMixin.java                                                |  10 +-
 src/main/java/blade/addon/mixin/SkullBlockEntityRendererMixin.java                                  |  44 -----
 src/main/java/blade/addon/mixin/SkullBlockRendererMixin.java                                        |  44 +++++
 src/main/java/blade/addon/mixin/StatusEffectsDisplayMixin.java                                      |  32 ----
 src/main/java/blade/addon/utils/Commands.java                                                       |  18 +-
 src/main/java/blade/addon/utils/Keybinds.java                                                       | 134 +++++++-------
 src/main/java/blade/addon/utils/Location.java                                                       |   4 +-
 src/main/java/blade/addon/utils/Misc.java                                                           |  68 +++----
 src/main/java/blade/addon/utils/Scheduler.java                                                      |  19 +-
 src/main/java/blade/addon/utils/Waypoint.java                                                       |  17 +-
 src/main/java/blade/addon/utils/config/Config.java                                                  | 519 ++++++++++++++++++++++++++--------------------------
 src/main/java/blade/addon/utils/config/components/CombinedScreenNotifications.java                  |   8 +-
 src/main/java/blade/addon/utils/config/components/CombinedTickTimer.java                            |   8 +-
 src/main/java/blade/addon/utils/config/values/ExtraOptions.java                                     |  10 +-
 src/main/java/blade/addon/utils/config/values/Floor7.java                                           |   6 +-
 src/main/java/blade/addon/utils/data/EntityUtil.java                                                |  59 +++---
 src/main/java/blade/addon/utils/data/ItemUtil.java                                                  |  51 +++---
 src/main/java/blade/addon/utils/data/TextUtil.java                                                  |  26 +--
 src/main/java/blade/addon/utils/debug/Debug.java                                                    |  34 ++--
 src/main/java/blade/addon/utils/dungeon/DungeonClass.java                                           |  15 +-
 src/main/java/blade/addon/utils/dungeon/FillHelper.java                                             |  34 ++--
 src/main/java/blade/addon/utils/dungeon/Phase.java                                                  |  15 +-
 src/main/java/blade/addon/utils/dungeon/Section.java                                                |  35 ++--
 src/main/java/blade/addon/utils/dungeon/Split.java                                                  |  34 ++--
 src/main/java/blade/addon/utils/events/interfaces/BlockEntityEvent.java                             |   2 +-
 src/main/java/blade/addon/utils/events/interfaces/BlockInteractionEvent.java                        |   4 +-
 src/main/java/blade/addon/utils/events/interfaces/EntityEvent.java                                  |   6 +-
 src/main/java/blade/addon/utils/events/interfaces/GameMessageEvent.java                             |   4 +-
 src/main/java/blade/addon/utils/events/interfaces/LeapEvent.java                                    |   4 +-
 src/main/java/blade/addon/utils/events/interfaces/PacketEvent.java                                  |   2 +-
 src/main/java/blade/addon/utils/events/interfaces/ParticleEvent.java                                |   4 +-
 src/main/java/blade/addon/utils/events/interfaces/PlaySoundEvent.java                               |   2 +-
 src/main/java/blade/addon/utils/events/interfaces/PlayerListEvent.java                              |   4 +-
 src/main/java/blade/addon/utils/events/interfaces/SlotChangeEvent.java                              |   2 +-
 src/main/java/blade/addon/utils/interfaces/GameHud.java                                             |   4 +-
 src/main/java/blade/addon/utils/rendering/RenderLayers.java                                         |  33 ++--
 src/main/java/blade/addon/utils/rendering/RenderPipelines.java                                      |  30 +--
 src/main/java/blade/addon/utils/rendering/RenderUtils.java                                          | 100 +++++-----
 src/main/java/blade/addon/utils/rendering/RenderingEvent.java                                       |   6 +-
 src/main/java/blade/addon/utils/rendering/RenderingEvents.java                                      |  82 ++++-----
 src/main/java/blade/addon/utils/rendering/drawevents/SlotEvent.java                                 |   6 +-
 src/main/java/blade/addon/utils/times/PersonalBest.java                                             |  14 +-
 src/main/resources/blade-addons.mixins.json                                                         |  36 ++--
 150 files changed, 2256 insertions(+), 2307 deletions(-)
diff --git a/build.gradle b/build.gradle
index afd5e5f..3aa226d 100644
--- a/build.gradle
+++ b/build.gradle
@@ -60,7 +60,8 @@ repositories {
 dependencies {
 	// To change the versions see the gradle.properties file
 	minecraft "com.mojang:minecraft:${project.minecraft_version}"
-	mappings "net.fabricmc:yarn:${project.yarn_mappings}:v2"
+	//mappings "net.fabricmc:yarn:${project.yarn_mappings}:v2"
+	mappings loom.officialMojangMappings()
 	modImplementation "net.fabricmc:fabric-loader:${project.loader_version}"
 
 	// Fabric API. This is technically optional, but you probably want it anyway.
diff --git a/gradle.properties b/gradle.properties
index cd2d845..8659370 100644
--- a/gradle.properties
+++ b/gradle.properties
@@ -13,13 +13,13 @@ loader_version=0.18.4
 loom_version=1.15-SNAPSHOT
 
 # Mod Properties
-mod_version=2.4.0
+mod_version=2.4.1
 maven_group=blade.addon
 archives_base_name=blade-addons
 
 # Dependencies
 fabric_version=0.141.3+1.21.11
 # I love dependencies sometimes breaking
-practical_config=1.4.3
-modmenu_version = 17.0.0-beta.2
+practical_config=ca2e4a5ea86f1f8ef9de3ae2bd3c1c1b6f6108ac
+modmenu_version =17.0.0-beta.2
 hypixel_api_version = 1.0.1
diff --git a/src/main/java/blade/addon/features/dungeon/AutoRequeue.java b/src/main/java/blade/addon/features/dungeon/AutoRequeue.java
index e22ba9b..cf5ed8e 100644
--- a/src/main/java/blade/addon/features/dungeon/AutoRequeue.java
+++ b/src/main/java/blade/addon/features/dungeon/AutoRequeue.java
@@ -5,8 +5,7 @@ import blade.addon.utils.Misc;
 import blade.addon.utils.config.values.Dungeons;
 import blade.addon.utils.events.Events;
 import net.fabricmc.fabric.api.client.message.v1.ClientReceiveMessageEvents;
-import net.minecraft.text.Text;
-
+import net.minecraft.network.chat.Component;
 import java.util.HashMap;
 import java.util.regex.Matcher;
 import java.util.regex.Pattern;
@@ -25,12 +24,12 @@ public class AutoRequeue {
             if (playerHashMap.containsKey(username)) {
                 if (message.contains("!undt")) {
                     playerHashMap.remove(username);
-                    Misc.addChatMessage(Text.literal("§aDowntime removed"));
+                    Misc.addChatMessage(Component.literal("§aDowntime removed"));
                 }
             } else {
                 if (message.contains("!dt")) {
                     playerHashMap.put(username, message);
-                    Misc.addChatMessage(Text.literal("§aDowntime added"));
+                    Misc.addChatMessage(Component.literal("§aDowntime added"));
                 }
             }
             return false;
@@ -51,8 +50,8 @@ public class AutoRequeue {
             if (playerHashMap.isEmpty()) {
                 Misc.executeCommand("instancerequeue");
             } else {
-                Misc.addChatMessage(Text.literal("Downtime reasons:"));
-                playerHashMap.forEach((name, string) -> Misc.addChatMessage((Text.literal(name + "> " + string))));
+                Misc.addChatMessage(Component.literal("Downtime reasons:"));
+                playerHashMap.forEach((name, string) -> Misc.addChatMessage((Component.literal(name + "> " + string))));
 
 
             }
diff --git a/src/main/java/blade/addon/features/dungeon/BloodNotifier.java b/src/main/java/blade/addon/features/dungeon/BloodNotifier.java
index 5654e39..dc4a9f4 100644
--- a/src/main/java/blade/addon/features/dungeon/BloodNotifier.java
+++ b/src/main/java/blade/addon/features/dungeon/BloodNotifier.java
@@ -8,9 +8,9 @@ import blade.addon.utils.dungeon.DungeonClass;
 import blade.addon.utils.events.Events;
 import blade.addon.utils.rendering.RenderUtils;
 import config.practical.hud.HUDComponent;
-import net.minecraft.client.gui.DrawContext;
-import net.minecraft.sound.SoundEvents;
-import net.minecraft.text.Text;
+import net.minecraft.client.gui.GuiGraphics;
+import net.minecraft.network.chat.Component;
+import net.minecraft.sounds.SoundEvents;
 
 public class BloodNotifier {
 
@@ -28,7 +28,7 @@ public class BloodNotifier {
                 prevTime = System.currentTimeMillis();
 
                 if (correctClass()) {
-                    Misc.sendSound(SoundEvents.ENTITY_EXPERIENCE_ORB_PICKUP, 2, 0);
+                    Misc.sendSound(SoundEvents.EXPERIENCE_ORB_PICKUP, 2, 0);
                 }
             }
 
@@ -53,8 +53,8 @@ public class BloodNotifier {
         return Dungeons.alertBloodSpawns && System.currentTimeMillis() - prevTime < 2000 && correctClass();
     }
 
-    public static void render(HUDComponent component, DrawContext context) {
-        RenderUtils.drawCenteredText(context, component, Text.literal("First four mobs spawned!"), Constants.RED);
+    public static void render(HUDComponent component, GuiGraphics context) {
+        RenderUtils.drawCenteredText(context, component, Component.literal("First four mobs spawned!"), Constants.RED);
     }
 
 }
diff --git a/src/main/java/blade/addon/features/dungeon/ChestCounter.java b/src/main/java/blade/addon/features/dungeon/ChestCounter.java
index 303c765..99564e5 100644
--- a/src/main/java/blade/addon/features/dungeon/ChestCounter.java
+++ b/src/main/java/blade/addon/features/dungeon/ChestCounter.java
@@ -9,13 +9,12 @@ import blade.addon.utils.dungeon.Phase;
 import blade.addon.utils.events.Events;
 import blade.addon.utils.rendering.RenderUtils;
 import config.practical.hud.HUDComponent;
-import net.minecraft.client.MinecraftClient;
-import net.minecraft.client.gui.DrawContext;
-import net.minecraft.sound.SoundEvents;
-import net.minecraft.text.Text;
-
 import java.util.regex.Matcher;
 import java.util.regex.Pattern;
+import net.minecraft.client.Minecraft;
+import net.minecraft.client.gui.GuiGraphics;
+import net.minecraft.network.chat.Component;
+import net.minecraft.sounds.SoundEvents;
 
 public class ChestCounter {
 
@@ -35,7 +34,7 @@ public class ChestCounter {
 
         Events.ON_PLAYER_ENTRY.register(receivedEntry -> {
             if (receivedEntry == null || !Location.in(Location.DUNGEON_HUB)) return false;
-            Text text = receivedEntry.displayName();
+            Component text = receivedEntry.displayName();
             if (text == null) return false;
 
             String string = text.getString();
@@ -54,8 +53,8 @@ public class ChestCounter {
         Events.ON_RUN_END.register(() -> {
             countedChests++;
             if (Dungeons.sendChestWarning && chestDisplayCount + countedChests >= Dungeons.chestWarningCount) {
-                Scheduler.scheduleSound(SoundEvents.BLOCK_NOTE_BLOCK_PLING.value(), 2, 1);
-                Misc.setTitle(Text.literal("§cChest count reached"));
+                Scheduler.scheduleSound(SoundEvents.NOTE_BLOCK_PLING.value(), 2, 1);
+                Misc.setTitle(Component.literal("§cChest count reached"));
             }
 
             return false;
@@ -67,14 +66,14 @@ public class ChestCounter {
         return Dungeons.displayChestCount && Location.inDungeon();
     }
 
-    public static void render(HUDComponent component, DrawContext context) {
+    public static void render(HUDComponent component, GuiGraphics context) {
         int x = component.getScaledX();
         int y = component.getScaledY();
 
         if (hasUpdatedData) {
             RenderUtils.drawPrefixedText(component, context, "Chests", Math.min(chestDisplayCount + countedChests, 60) + "");
         } else {
-            context.drawText(MinecraftClient.getInstance().textRenderer, Text.literal("go to the dungeon hub"), x, y, Constants.RED, true);
+            context.drawString(Minecraft.getInstance().font, Component.literal("go to the dungeon hub"), x, y, Constants.RED, true);
 
         }
     }
diff --git a/src/main/java/blade/addon/features/dungeon/ExplosiveShot.java b/src/main/java/blade/addon/features/dungeon/ExplosiveShot.java
index 37f47de..ba9f0d3 100644
--- a/src/main/java/blade/addon/features/dungeon/ExplosiveShot.java
+++ b/src/main/java/blade/addon/features/dungeon/ExplosiveShot.java
@@ -5,11 +5,10 @@ import blade.addon.utils.Misc;
 import blade.addon.utils.config.values.Dungeons;
 import blade.addon.utils.dungeon.Phase;
 import blade.addon.utils.events.Events;
-import net.minecraft.text.Text;
-
 import java.text.DecimalFormat;
 import java.util.regex.Matcher;
 import java.util.regex.Pattern;
+import net.minecraft.network.chat.Component;
 
 public class ExplosiveShot {
 
@@ -37,7 +36,7 @@ public class ExplosiveShot {
 
                     double damagePerEntity = amountNum / countNum;
 
-                    Misc.addChatMessage(Text.literal("§aExplosive shot did §e" + FORMAT.format(damagePerEntity) +"§a damage per enemy."));
+                    Misc.addChatMessage(Component.literal("§aExplosive shot did §e" + FORMAT.format(damagePerEntity) +"§a damage per enemy."));
 
                 } catch (NumberFormatException ignored) {
                 }
diff --git a/src/main/java/blade/addon/features/dungeon/HidePlayers.java b/src/main/java/blade/addon/features/dungeon/HidePlayers.java
index b7cf9b0..ed6506f 100644
--- a/src/main/java/blade/addon/features/dungeon/HidePlayers.java
+++ b/src/main/java/blade/addon/features/dungeon/HidePlayers.java
@@ -6,9 +6,9 @@ import blade.addon.utils.config.values.Dungeons;
 import blade.addon.utils.data.EntityUtil;
 import blade.addon.utils.dungeon.Phase;
 import blade.addon.utils.events.Events;
-import net.minecraft.client.MinecraftClient;
-import net.minecraft.client.network.ClientPlayerEntity;
-import net.minecraft.entity.player.PlayerEntity;
+import net.minecraft.client.Minecraft;
+import net.minecraft.client.player.LocalPlayer;
+import net.minecraft.world.entity.player.Player;
 
 public class HidePlayers {
 
@@ -38,25 +38,25 @@ public class HidePlayers {
         return true;
     }
 
-    public static boolean testHideAtSS(PlayerEntity player) {
+    public static boolean testHideAtSS(Player player) {
         if (!Dungeons.hideAtSS || !Phase.inBoss()) return false;
         if (Dungeons.hideBeforeTermsOnly && Phase.inP3()) return false;
 
         return DeviceNotifier.atSS(player);
     }
 
-    public static boolean testHideInRange(PlayerEntity player) {
+    public static boolean testHideInRange(Player player) {
         if (!Dungeons.hidePlayersInRange) return false;
-        ClientPlayerEntity clientPlayer = MinecraftClient.getInstance().player;
+        LocalPlayer clientPlayer = Minecraft.getInstance().player;
         if (clientPlayer == null) return false;
 
-        double distance = player.getEntityPos().distanceTo(clientPlayer.getEntityPos());
+        double distance = player.position().distanceTo(clientPlayer.position());
         if (!Double.isFinite(distance) || !Double.isFinite(Dungeons.hidePlayerRange)) return false;
         return distance <= Dungeons.hidePlayerRange;
 
     }
 
-    public static boolean shouldHidePlayers(PlayerEntity player) {
+    public static boolean shouldHidePlayers(Player player) {
         if (!Location.inDungeon() || !EntityUtil.isARealPlayer(player) || EntityUtil.isClientPlayer(player)) return false;
 
         if (testHideAtLeap()) {
diff --git a/src/main/java/blade/addon/features/dungeon/ItemHighlight.java b/src/main/java/blade/addon/features/dungeon/ItemHighlight.java
index 0050920..f327c92 100644
--- a/src/main/java/blade/addon/features/dungeon/ItemHighlight.java
+++ b/src/main/java/blade/addon/features/dungeon/ItemHighlight.java
@@ -6,24 +6,22 @@ import blade.addon.utils.config.values.Dungeons;
 import blade.addon.utils.dungeon.Phase;
 import blade.addon.utils.rendering.RenderUtils;
 import blade.addon.utils.rendering.RenderingEvents;
+import com.mojang.blaze3d.vertex.PoseStack;
+import com.mojang.blaze3d.vertex.VertexConsumer;
 import net.fabricmc.fabric.api.client.event.lifecycle.v1.ClientEntityEvents;
 import net.fabricmc.fabric.api.client.event.lifecycle.v1.ClientTickEvents;
 import net.fabricmc.fabric.api.client.rendering.v1.world.WorldRenderContext;
-import net.minecraft.client.MinecraftClient;
-import net.minecraft.client.network.ClientPlayerEntity;
-import net.minecraft.client.render.DrawStyle;
-import net.minecraft.client.render.VertexConsumer;
-import net.minecraft.client.render.VertexRendering;
-import net.minecraft.client.util.math.MatrixStack;
-import net.minecraft.client.world.ClientWorld;
-import net.minecraft.entity.EntityDimensions;
-import net.minecraft.entity.ItemEntity;
-import net.minecraft.text.Text;
-import net.minecraft.util.math.Box;
-import net.minecraft.util.math.ColorHelper;
-import net.minecraft.util.math.MathHelper;
-import net.minecraft.world.debug.gizmo.GizmoDrawing;
-
+import net.minecraft.client.Minecraft;
+import net.minecraft.client.multiplayer.ClientLevel;
+import net.minecraft.client.player.LocalPlayer;
+import net.minecraft.gizmos.GizmoStyle;
+import net.minecraft.gizmos.Gizmos;
+import net.minecraft.network.chat.Component;
+import net.minecraft.util.ARGB;
+import net.minecraft.util.Mth;
+import net.minecraft.world.entity.EntityDimensions;
+import net.minecraft.world.entity.item.ItemEntity;
+import net.minecraft.world.phys.AABB;
 import java.util.HashSet;
 import java.util.List;
 import java.util.concurrent.ConcurrentHashMap;
@@ -37,10 +35,10 @@ public class ItemHighlight {
 
     public static void init() {
         ClientTickEvents.END_CLIENT_TICK.register(minecraftClient -> {
-            ClientWorld world = minecraftClient.world;
+            ClientLevel world = minecraftClient.level;
             if (world == null) return;
 
-            world.getEntities().forEach(entity -> {
+            world.entitiesForRendering().forEach(entity -> {
                 if (entity instanceof ItemEntity item) {
                     if (trackedItems.containsKey(item)) return;
 
@@ -61,27 +59,27 @@ public class ItemHighlight {
         RenderingEvents.NO_DEPTH_FILLED.register(ItemHighlight::render);
     }
 
-    private static void render(WorldRenderContext context, MatrixStack matrixStack, VertexConsumer consumer) {
+    private static void render(WorldRenderContext context, PoseStack matrixStack, VertexConsumer consumer) {
         if (!Dungeons.highlightItems) return;
-        double tickProgress = MinecraftClient.getInstance().getRenderTickCounter().getTickProgress(false);
+        double tickProgress = Minecraft.getInstance().getDeltaTracker().getGameTimeDeltaPartialTick(false);
 
 
         trackedItems.forEach((itemEntity, integer) -> {
-            double x =  MathHelper.lerp(tickProgress, itemEntity.lastRenderX, itemEntity.getX());
-            double y =  MathHelper.lerp(tickProgress, itemEntity.lastRenderY, itemEntity.getY());
-            double z =  MathHelper.lerp(tickProgress, itemEntity.lastRenderZ, itemEntity.getZ());
+            double x =  Mth.lerp(tickProgress, itemEntity.xOld, itemEntity.getX());
+            double y =  Mth.lerp(tickProgress, itemEntity.yOld, itemEntity.getY());
+            double z =  Mth.lerp(tickProgress, itemEntity.zOld, itemEntity.getZ());
 
             EntityDimensions dimension = itemEntity.getDimensions(itemEntity.getPose());
-            Box box = dimension.getBoxAt(x, y, z).expand(0.1).offset(0, 0.05, 0);
+            AABB box = dimension.makeBoundingBox(x, y, z).inflate(0.1).move(0, 0.05, 0);
             float[] color = RenderUtils.toFloats(getColor(itemEntity));
-            GizmoDrawing.box(box, DrawStyle.filled(ColorHelper.fromFloats(color[3], color[0], color[1], color[2])));
+            Gizmos.cuboid(box, GizmoStyle.fill(ARGB.colorFromFloat(color[3], color[0], color[1], color[2])));
         });
     }
 
     public static boolean highlightItem(ItemEntity item) {
         if (!Dungeons.highlightItems || !Location.inDungeon() || Phase.inBoss()) return false;
 
-        Text itemText = item.getStack().getName();
+        Component itemText = item.getItem().getHoverName();
         if (itemText == null) return false;
         String itemName = itemText.getString();
 
@@ -89,14 +87,14 @@ public class ItemHighlight {
     }
 
     public static int getColor(ItemEntity item) {
-        ClientPlayerEntity player = MinecraftClient.getInstance().player;
+        LocalPlayer player = Minecraft.getInstance().player;
         if (player == null) return 0;
-        double distance = item.getEntityPos().distanceTo(player.getEntityPos());
+        double distance = item.position().distanceTo(player.position());
         if (distance > 20) {
             return 0;
         } else if (distance > 3.5) {
             return Constants.RED;
-        } else if (item.age > 11){
+        } else if (item.tickCount > 11){
             return Constants.GREEN;
         } else {
             return Constants.GOLD;
diff --git a/src/main/java/blade/addon/features/dungeon/KeyNotifier.java b/src/main/java/blade/addon/features/dungeon/KeyNotifier.java
index 3f09f6d..95b1eff 100644
--- a/src/main/java/blade/addon/features/dungeon/KeyNotifier.java
+++ b/src/main/java/blade/addon/features/dungeon/KeyNotifier.java
@@ -9,25 +9,24 @@ import blade.addon.utils.events.Events;
 import blade.addon.utils.rendering.RenderUtils;
 import config.practical.hud.HUDComponent;
 import net.fabricmc.fabric.api.client.event.lifecycle.v1.ClientTickEvents;
-import net.minecraft.client.MinecraftClient;
-import net.minecraft.client.gui.DrawContext;
-import net.minecraft.component.DataComponentTypes;
-import net.minecraft.component.type.ProfileComponent;
-import net.minecraft.entity.Entity;
-import net.minecraft.entity.EquipmentSlot;
-import net.minecraft.entity.decoration.ArmorStandEntity;
-import net.minecraft.item.ItemStack;
-import net.minecraft.sound.SoundEvents;
-import net.minecraft.text.Text;
-
+import net.minecraft.client.Minecraft;
+import net.minecraft.client.gui.GuiGraphics;
+import net.minecraft.core.component.DataComponents;
+import net.minecraft.network.chat.Component;
+import net.minecraft.sounds.SoundEvents;
+import net.minecraft.world.entity.Entity;
+import net.minecraft.world.entity.EquipmentSlot;
+import net.minecraft.world.entity.decoration.ArmorStand;
+import net.minecraft.world.item.ItemStack;
+import net.minecraft.world.item.component.ResolvableProfile;
 import java.util.concurrent.CopyOnWriteArrayList;
 import java.util.regex.Matcher;
 import java.util.regex.Pattern;
 
 public class KeyNotifier {
 
-    private final static Text BLOOD_KEY = Text.literal("§l§cBlood Key Dropped");
-    private final static Text WITHER_KEY = Text.literal("§l§7Wither Key Dropped");
+    private final static Component BLOOD_KEY = Component.literal("§l§cBlood Key Dropped");
+    private final static Component WITHER_KEY = Component.literal("§l§7Wither Key Dropped");
 
     //Thanks to mio for this key detection system
     private final static String WITHER_UUID = "2865274b-3097-394e-8149-ec629c72d850";
@@ -45,19 +44,19 @@ public class KeyNotifier {
         ClientTickEvents.END_WORLD_TICK.register((world) -> {
             if (!Location.inDungeon() || hasKey || !Dungeons.enableKeyNotifier || Phase.inBoss()) return;
 
-            for (Entity entity : world.getEntities()) {
-                if (entity instanceof ArmorStandEntity armorStand && !foundKeys.contains(entity)) {
-                    ItemStack head = armorStand.getEquippedStack(EquipmentSlot.HEAD);
-                    ProfileComponent profile = head.get(DataComponentTypes.PROFILE);
+            for (Entity entity : world.entitiesForRendering()) {
+                if (entity instanceof ArmorStand armorStand && !foundKeys.contains(entity)) {
+                    ItemStack head = armorStand.getItemBySlot(EquipmentSlot.HEAD);
+                    ResolvableProfile profile = head.get(DataComponents.PROFILE);
                     if (profile == null) continue;
-                    String uuid = profile.getGameProfile().id().toString();
+                    String uuid = profile.partialProfile().id().toString();
                     if (uuid == null) continue;
 
                     if (uuid.equals(WITHER_UUID)) {
                         foundKeys.add(entity);
                         hasKey = true;
                         if (isValidClass()) {
-                            Scheduler.scheduleSound(SoundEvents.ENTITY_EXPERIENCE_ORB_PICKUP, 2, 0.5f);
+                            Scheduler.scheduleSound(SoundEvents.EXPERIENCE_ORB_PICKUP, 2, 0.5f);
                         }
                         return;
                     }
@@ -68,7 +67,7 @@ public class KeyNotifier {
                         hasKey = true;
 
                         if (isValidClass()) {
-                            Scheduler.scheduleSound(SoundEvents.ENTITY_EXPERIENCE_ORB_PICKUP, 2, 0.5f);
+                            Scheduler.scheduleSound(SoundEvents.EXPERIENCE_ORB_PICKUP, 2, 0.5f);
                         }
                         return;
                     }
@@ -114,11 +113,11 @@ public class KeyNotifier {
         return Location.inDungeon() && hasKey && isValidClass() && !Phase.inBoss();
     }
 
-    public static void render(HUDComponent component, DrawContext context) {
+    public static void render(HUDComponent component, GuiGraphics context) {
         int x = component.getScaledX();
         int y = component.getScaledY();
 
-        Text text = isBloodKey ? BLOOD_KEY : WITHER_KEY;
-        RenderUtils.drawCenteredText(context, MinecraftClient.getInstance().textRenderer, text, x, y, component.getWidth(), 0xffffffff);
+        Component text = isBloodKey ? BLOOD_KEY : WITHER_KEY;
+        RenderUtils.drawCenteredText(context, Minecraft.getInstance().font, text, x, y, component.getWidth(), 0xffffffff);
     }
 }
diff --git a/src/main/java/blade/addon/features/dungeon/LeapMessage.java b/src/main/java/blade/addon/features/dungeon/LeapMessage.java
index 1e3fa56..aa87ba8 100644
--- a/src/main/java/blade/addon/features/dungeon/LeapMessage.java
+++ b/src/main/java/blade/addon/features/dungeon/LeapMessage.java
@@ -2,8 +2,8 @@ package blade.addon.features.dungeon;
 
 import blade.addon.utils.config.values.Dungeons;
 import blade.addon.utils.events.Events;
-import net.minecraft.client.MinecraftClient;
-import net.minecraft.client.network.ClientPlayNetworkHandler;
+import net.minecraft.client.Minecraft;
+import net.minecraft.client.multiplayer.ClientPacketListener;
 
 public class LeapMessage {
 
@@ -13,9 +13,9 @@ public class LeapMessage {
         Events.ON_LEAP.register(message -> {
             if (!Dungeons.enableLeapMessages) return false;
 
-            ClientPlayNetworkHandler networkHandler = MinecraftClient.getInstance().getNetworkHandler();
+            ClientPacketListener networkHandler = Minecraft.getInstance().getConnection();
             if (networkHandler == null) return false;
-            networkHandler.sendChatCommand("pc " + message.getString().substring(INDEX_REMOVAL));
+            networkHandler.sendCommand("pc " + message.getString().substring(INDEX_REMOVAL));
             return false;
         });
     }
diff --git a/src/main/java/blade/addon/features/dungeon/LeapOrder.java b/src/main/java/blade/addon/features/dungeon/LeapOrder.java
index 0c9f6d5..136df4f 100644
--- a/src/main/java/blade/addon/features/dungeon/LeapOrder.java
+++ b/src/main/java/blade/addon/features/dungeon/LeapOrder.java
@@ -3,16 +3,15 @@ package blade.addon.features.dungeon;
 import blade.addon.utils.debug.Debug;
 import blade.addon.utils.Misc;
 import blade.addon.utils.dungeon.DungeonClass;
-import net.minecraft.client.MinecraftClient;
-import net.minecraft.client.network.ClientPlayerEntity;
-import net.minecraft.client.world.ClientWorld;
-import net.minecraft.scoreboard.Scoreboard;
-import net.minecraft.scoreboard.Team;
-import net.minecraft.text.Text;
-
 import java.util.HashMap;
 import java.util.regex.Matcher;
 import java.util.regex.Pattern;
+import net.minecraft.client.Minecraft;
+import net.minecraft.client.multiplayer.ClientLevel;
+import net.minecraft.client.player.LocalPlayer;
+import net.minecraft.network.chat.Component;
+import net.minecraft.world.scores.PlayerTeam;
+import net.minecraft.world.scores.Scoreboard;
 
 public class LeapOrder {
 
@@ -31,16 +30,16 @@ public class LeapOrder {
         HashMap<DungeonClass, String> classNameMap = new HashMap<>();
         DungeonClass playersClass = null;
 
-        ClientPlayerEntity player = MinecraftClient.getInstance().player;
-        ClientWorld world = MinecraftClient.getInstance().world;
+        LocalPlayer player = Minecraft.getInstance().player;
+        ClientLevel world = Minecraft.getInstance().level;
         if (player == null || world == null) {
             Debug.LOGGER.warn("Player or World is null");
             return;
         }
 
         Scoreboard scoreboard = world.getScoreboard();
-        for (Team team : scoreboard.getTeams()) {
-            String teamStr = (team.getPrefix().getString() + team.getSuffix().getString()).replaceAll("§.", "");
+        for (PlayerTeam team : scoreboard.getPlayerTeams()) {
+            String teamStr = (team.getPlayerPrefix().getString() + team.getPlayerSuffix().getString()).replaceAll("§.", "");
             Matcher matcher = PATTERN.matcher(teamStr);
             if (matcher.find()) {
                 DungeonClass dungeonClass = getClass(matcher.group(1));
@@ -61,7 +60,7 @@ public class LeapOrder {
         }
 
         if (classNameMap.size() != 4 || playersClass == null) {
-            Misc.addChatMessage(Text.literal("Cant find all players or the players class"));
+            Misc.addChatMessage(Component.literal("Cant find all players or the players class"));
             return;
         }
         DungeonClass[] order;
@@ -78,7 +77,7 @@ public class LeapOrder {
         }
 
         String leapOrder = "odin leaporder " + classNameMap.get(order[0]) + " " + classNameMap.get(order[1]) + " " + classNameMap.get(order[2]) + " " + classNameMap.get(order[3]);
-        Misc.addChatMessage(Text.literal("Executing command: " + leapOrder));
+        Misc.addChatMessage(Component.literal("Executing command: " + leapOrder));
         Misc.executeCommand(leapOrder);
     }
 
diff --git a/src/main/java/blade/addon/features/dungeon/QuizTimer.java b/src/main/java/blade/addon/features/dungeon/QuizTimer.java
index 5f27038..f694db3 100644
--- a/src/main/java/blade/addon/features/dungeon/QuizTimer.java
+++ b/src/main/java/blade/addon/features/dungeon/QuizTimer.java
@@ -7,10 +7,10 @@ import blade.addon.utils.config.values.ExtraOptions;
 import blade.addon.utils.events.Events;
 import blade.addon.utils.rendering.RenderUtils;
 import config.practical.hud.HUDComponent;
-import net.minecraft.client.MinecraftClient;
-import net.minecraft.client.font.TextRenderer;
-import net.minecraft.client.gui.DrawContext;
-import net.minecraft.text.Text;
+import net.minecraft.client.Minecraft;
+import net.minecraft.client.gui.Font;
+import net.minecraft.client.gui.GuiGraphics;
+import net.minecraft.network.chat.Component;
 
 public class QuizTimer {
 
@@ -56,16 +56,16 @@ public class QuizTimer {
         return Dungeons.quizTimer && tick > 0;
     }
 
-    public static void render(HUDComponent component, DrawContext context) {
-        TextRenderer textRenderer = MinecraftClient.getInstance().textRenderer;
+    public static void render(HUDComponent component, GuiGraphics context) {
+        Font textRenderer = Minecraft.getInstance().font;
         if (textRenderer == null) return;
 
         if (Dungeons.quizProgress) {
-            Text drawnText = Text.literal("Quiz " + "(").withColor(ExtraOptions.timerPrefixColor)
-                    .append(Text.literal(stage + "/3").withColor(0xffffffff)
-                            .append(Text.literal("): ").withColor(ExtraOptions.timerPrefixColor))
-                            .append(Text.literal(Constants.DECIMAL_FORMAT.format(tick * Constants.TICK_DURATION) + "s").withColor(0xffffffff)));
-            context.drawText(textRenderer, drawnText, component.getScaledX(), component.getScaledY(), 0xffffffff, true);
+            Component drawnText = Component.literal("Quiz " + "(").withColor(ExtraOptions.timerPrefixColor)
+                    .append(Component.literal(stage + "/3").withColor(0xffffffff)
+                            .append(Component.literal("): ").withColor(ExtraOptions.timerPrefixColor))
+                            .append(Component.literal(Constants.DECIMAL_FORMAT.format(tick * Constants.TICK_DURATION) + "s").withColor(0xffffffff)));
+            context.drawString(textRenderer, drawnText, component.getScaledX(), component.getScaledY(), 0xffffffff, true);
         } else {
             RenderUtils.drawPrefixedTimer(component, context, "Quiz", tick);
         }
diff --git a/src/main/java/blade/addon/features/dungeon/RunStartValidator.java b/src/main/java/blade/addon/features/dungeon/RunStartValidator.java
index 0cda68a..9fb0815 100644
--- a/src/main/java/blade/addon/features/dungeon/RunStartValidator.java
+++ b/src/main/java/blade/addon/features/dungeon/RunStartValidator.java
@@ -8,27 +8,26 @@ import blade.addon.utils.dungeon.Phase;
 import blade.addon.utils.events.Events;
 import blade.addon.utils.rendering.RenderUtils;
 import config.practical.hud.HUDComponent;
-import net.minecraft.client.MinecraftClient;
-import net.minecraft.client.gui.DrawContext;
-import net.minecraft.client.network.ClientPlayerEntity;
-import net.minecraft.client.world.ClientWorld;
-import net.minecraft.scoreboard.Scoreboard;
-import net.minecraft.scoreboard.Team;
-import net.minecraft.sound.SoundEvents;
-import net.minecraft.text.Text;
-
 import java.util.HashMap;
 import java.util.Map;
 import java.util.regex.Matcher;
 import java.util.regex.Pattern;
+import net.minecraft.client.Minecraft;
+import net.minecraft.client.gui.GuiGraphics;
+import net.minecraft.client.multiplayer.ClientLevel;
+import net.minecraft.client.player.LocalPlayer;
+import net.minecraft.network.chat.Component;
+import net.minecraft.sounds.SoundEvents;
+import net.minecraft.world.scores.PlayerTeam;
+import net.minecraft.world.scores.Scoreboard;
 
 public class RunStartValidator {
 
     private static final Pattern STARTING_PATTERN = Pattern.compile("^Starting in \\d second(s)?");
     private static final Pattern CLASS_PATTERN = Pattern.compile("\\[([ABHMT])]");
 
-    private static final Text DUPE_CLASS_TEXT = Text.literal("§cDuplicate Class Detected");
-    private static final Text PLAYER_COUNT_TEXT = Text.literal("§cNot enough players");
+    private static final Component DUPE_CLASS_TEXT = Component.literal("§cDuplicate Class Detected");
+    private static final Component PLAYER_COUNT_TEXT = Component.literal("§cNot enough players");
 
     private static boolean hasDupeClasses = false;
     private static boolean notEnoughPlayers = false;
@@ -44,7 +43,7 @@ public class RunStartValidator {
             hasTicked = true;
 
             if (validate()) {
-                Scheduler.scheduleSound(SoundEvents.BLOCK_NOTE_BLOCK_PLING.value(), 2, 1);
+                Scheduler.scheduleSound(SoundEvents.NOTE_BLOCK_PLING.value(), 2, 1);
             }
             return false;
         });
@@ -69,8 +68,8 @@ public class RunStartValidator {
     }
 
     private static boolean validate() {
-        ClientWorld world = MinecraftClient.getInstance().world;
-        ClientPlayerEntity player = MinecraftClient.getInstance().player;
+        ClientLevel world = Minecraft.getInstance().level;
+        LocalPlayer player = Minecraft.getInstance().player;
         if (world == null || player == null) return false;
 
         Scoreboard scoreboard = world.getScoreboard();
@@ -118,8 +117,8 @@ public class RunStartValidator {
     private static HashMap<String, Integer> readScoreBoard(Scoreboard scoreboard) {
         HashMap<String, Integer> map = new HashMap<>();
 
-        for (Team team : scoreboard.getTeams()) {
-            String teamStr = team.getPrefix().getString() + team.getSuffix().getString();
+        for (PlayerTeam team : scoreboard.getPlayerTeams()) {
+            String teamStr = team.getPlayerPrefix().getString() + team.getPlayerSuffix().getString();
             Matcher matcher = CLASS_PATTERN.matcher(teamStr);
             if (!matcher.find()) continue;
 
@@ -135,13 +134,13 @@ public class RunStartValidator {
         return Location.inDungeon() && !Phase.runStarted() && (hasDupeClasses || notEnoughPlayers);
     }
 
-    public static void render(HUDComponent component, DrawContext context) {
+    public static void render(HUDComponent component, GuiGraphics context) {
         if (hasDupeClasses) {
             RenderUtils.drawCenteredText(context, component, DUPE_CLASS_TEXT);
         } else if (notEnoughPlayers) {
             RenderUtils.drawCenteredText(context, component, PLAYER_COUNT_TEXT);
         } else {
-            RenderUtils.drawCenteredText(context, component, Text.literal("§cSome warning text"));
+            RenderUtils.drawCenteredText(context, component, Component.literal("§cSome warning text"));
         }
     }
 }
diff --git a/src/main/java/blade/addon/features/dungeon/SecretSpawnTimer.java b/src/main/java/blade/addon/features/dungeon/SecretSpawnTimer.java
index d6e1c83..bc7b8b4 100644
--- a/src/main/java/blade/addon/features/dungeon/SecretSpawnTimer.java
+++ b/src/main/java/blade/addon/features/dungeon/SecretSpawnTimer.java
@@ -7,12 +7,11 @@ import blade.addon.utils.dungeon.Phase;
 import blade.addon.utils.events.Events;
 import blade.addon.utils.rendering.RenderUtils;
 import config.practical.hud.HUDComponent;
-import net.minecraft.client.MinecraftClient;
-import net.minecraft.client.gui.DrawContext;
-import net.minecraft.text.Text;
-
 import java.util.regex.Matcher;
 import java.util.regex.Pattern;
+import net.minecraft.client.Minecraft;
+import net.minecraft.client.gui.GuiGraphics;
+import net.minecraft.network.chat.Component;
 
 public class SecretSpawnTimer {
 
@@ -42,13 +41,13 @@ public class SecretSpawnTimer {
         return Location.inDungeon() && Dungeons.enableSecretSpawnTimer && !Phase.inBoss() && Phase.runStarted();
     }
 
-    public static void render(HUDComponent component, DrawContext context) {
+    public static void render(HUDComponent component, GuiGraphics context) {
         int x = component.getScaledX();
         int y = component.getScaledY();
 
         int color = tick > 10 ? Constants.GREEN : tick > 5 ? Constants.GOLD : Constants.RED;
 
-        RenderUtils.drawCenteredText(context, MinecraftClient.getInstance().textRenderer, Text.literal(tick + ""), x, y, component.getWidth(), color);
+        RenderUtils.drawCenteredText(context, Minecraft.getInstance().font, Component.literal(tick + ""), x, y, component.getWidth(), color);
 
     }
 }
diff --git a/src/main/java/blade/addon/features/dungeon/WarpCooldown.java b/src/main/java/blade/addon/features/dungeon/WarpCooldown.java
index a7ea754..92a9623 100644
--- a/src/main/java/blade/addon/features/dungeon/WarpCooldown.java
+++ b/src/main/java/blade/addon/features/dungeon/WarpCooldown.java
@@ -4,10 +4,9 @@ import blade.addon.utils.config.values.Dungeons;
 import blade.addon.utils.events.Events;
 import blade.addon.utils.rendering.RenderUtils;
 import config.practical.hud.HUDComponent;
-import net.minecraft.client.gui.DrawContext;
-
 import java.util.regex.Matcher;
 import java.util.regex.Pattern;
+import net.minecraft.client.gui.GuiGraphics;
 
 public class WarpCooldown {
 
@@ -43,7 +42,7 @@ public class WarpCooldown {
         }
     }
 
-    public static void render(HUDComponent component, DrawContext context) {
+    public static void render(HUDComponent component, GuiGraphics context) {
         double timeRemaining = Math.max((endTime - System.currentTimeMillis()) / 1000.0, 0);
         RenderUtils.drawPrefixedTimer(component, context, "Warp Cooldown", timeRemaining);
     }
diff --git a/src/main/java/blade/addon/features/dungeon/f7/BossWaypoints.java b/src/main/java/blade/addon/features/dungeon/f7/BossWaypoints.java
index 61047c4..4e59751 100644
--- a/src/main/java/blade/addon/features/dungeon/f7/BossWaypoints.java
+++ b/src/main/java/blade/addon/features/dungeon/f7/BossWaypoints.java
@@ -13,18 +13,17 @@ import com.google.gson.Gson;
 import com.google.gson.JsonArray;
 import com.google.gson.JsonElement;
 import com.google.gson.JsonObject;
+import com.mojang.blaze3d.vertex.PoseStack;
+import com.mojang.blaze3d.vertex.VertexConsumer;
 import net.fabricmc.fabric.api.client.rendering.v1.world.WorldRenderContext;
 import net.fabricmc.fabric.api.event.player.UseBlockCallback;
-import net.minecraft.client.render.VertexConsumer;
-import net.minecraft.client.util.math.MatrixStack;
-import net.minecraft.entity.player.PlayerEntity;
-import net.minecraft.text.Text;
-import net.minecraft.util.ActionResult;
-import net.minecraft.util.Hand;
-import net.minecraft.util.hit.BlockHitResult;
-import net.minecraft.util.math.BlockPos;
-import net.minecraft.world.World;
-
+import net.minecraft.core.BlockPos;
+import net.minecraft.network.chat.Component;
+import net.minecraft.world.InteractionHand;
+import net.minecraft.world.InteractionResult;
+import net.minecraft.world.entity.player.Player;
+import net.minecraft.world.level.Level;
+import net.minecraft.world.phys.BlockHitResult;
 import java.io.FileWriter;
 import java.io.IOException;
 import java.nio.file.Files;
@@ -66,12 +65,12 @@ public class BossWaypoints {
 
     public static void setPlace(boolean shouldPlace) {
         place = shouldPlace;
-        Misc.addChatMessage(Text.literal("Edit mode: ").append(Misc.getStatusText(place)));
+        Misc.addChatMessage(Component.literal("Edit mode: ").append(Misc.getStatusText(place)));
     }
 
     public static void setIgnoreBoss(boolean shouldIgnoreBoss) {
         ignoreBoss = shouldIgnoreBoss;
-        Misc.addChatMessage(Text.literal("Ignore boss: ").append(Misc.getStatusText(ignoreBoss)));
+        Misc.addChatMessage(Component.literal("Ignore boss: ").append(Misc.getStatusText(ignoreBoss)));
     }
 
     public static void togglePlace() {
@@ -116,9 +115,9 @@ public class BossWaypoints {
         save();
     }
 
-    private static ActionResult onBlock(PlayerEntity playerEntity, World world, Hand hand, BlockHitResult blockHitResult) {
-        if (!isInValidArea() || !EntityUtil.isClientPlayer(playerEntity)) return ActionResult.PASS;
-        if (hand == Hand.OFF_HAND) return ActionResult.PASS;
+    private static InteractionResult onBlock(Player playerEntity, Level world, InteractionHand hand, BlockHitResult blockHitResult) {
+        if (!isInValidArea() || !EntityUtil.isClientPlayer(playerEntity)) return InteractionResult.PASS;
+        if (hand == InteractionHand.OFF_HAND) return InteractionResult.PASS;
 
         BlockPos pos = blockHitResult.getBlockPos();
 
@@ -126,7 +125,7 @@ public class BossWaypoints {
         int y = pos.getY();
         int z = pos.getZ();
 
-        if (!place) return ActionResult.PASS;
+        if (!place) return InteractionResult.PASS;
 
         Waypoint waypoint = getWaypoint(x, y, z);
         if (waypoint == null) {
@@ -136,10 +135,10 @@ public class BossWaypoints {
         }
 
         save();
-        return ActionResult.PASS;
+        return InteractionResult.PASS;
     }
 
-    private static void render(WorldRenderContext context, MatrixStack matrixStack, VertexConsumer consumer) {
+    private static void render(WorldRenderContext context, PoseStack matrixStack, VertexConsumer consumer) {
         if (!isInValidArea()) return;
 
         waypoints.forEach(waypoint -> {
@@ -149,7 +148,7 @@ public class BossWaypoints {
         });
     }
 
-    private static void renderThroughWall(WorldRenderContext context, MatrixStack matrixStack, VertexConsumer consumer) {
+    private static void renderThroughWall(WorldRenderContext context, PoseStack matrixStack, VertexConsumer consumer) {
         if (!isInValidArea()) return;
 
         waypoints.forEach(waypoint -> {
diff --git a/src/main/java/blade/addon/features/dungeon/f7/PredevTimer.java b/src/main/java/blade/addon/features/dungeon/f7/PredevTimer.java
index bc7d91e..499b1e4 100644
--- a/src/main/java/blade/addon/features/dungeon/f7/PredevTimer.java
+++ b/src/main/java/blade/addon/features/dungeon/f7/PredevTimer.java
@@ -7,8 +7,8 @@ import blade.addon.utils.dungeon.Phase;
 import blade.addon.utils.events.Events;
 import blade.addon.utils.times.PersonalBests;
 import net.fabricmc.fabric.api.client.event.lifecycle.v1.ClientTickEvents;
-import net.minecraft.client.network.ClientPlayerEntity;
-import net.minecraft.text.Text;
+import net.minecraft.client.player.LocalPlayer;
+import net.minecraft.network.chat.Component;
 
 public class PredevTimer {
 
@@ -33,7 +33,7 @@ public class PredevTimer {
 
         ClientTickEvents.END_CLIENT_TICK.register(client -> {
             if (!shouldTrack || at3rdDev || (!Floor7.predevForAll && !DungeonClass.isClass(DungeonClass.HEALER))) return;
-            ClientPlayerEntity player = client.player;
+            LocalPlayer player = client.player;
             if (player == null) return;
             if (Misc.getDistance(player.getX(), player.getZ(), 1, 77) <= 3) {
                 at3rdDev = true;
@@ -42,7 +42,7 @@ public class PredevTimer {
 
         Events.ON_LEAP.register(message -> {
             if (at3rdDev && shouldTrack && (DungeonClass.isClass(DungeonClass.HEALER) || Floor7.predevForAll)) {
-                PersonalBests.predevTime.testNewTime(Text.literal("§aPredev completed in "), bossEnterTime);
+                PersonalBests.predevTime.testNewTime(Component.literal("§aPredev completed in "), bossEnterTime);
                 at3rdDev = false;
                 shouldTrack = false;
             }
diff --git a/src/main/java/blade/addon/features/dungeon/f7/RelicTimer.java b/src/main/java/blade/addon/features/dungeon/f7/RelicTimer.java
index f6f1237..8c983e2 100644
--- a/src/main/java/blade/addon/features/dungeon/f7/RelicTimer.java
+++ b/src/main/java/blade/addon/features/dungeon/f7/RelicTimer.java
@@ -12,45 +12,43 @@ import blade.addon.utils.events.Events;
 import blade.addon.utils.rendering.RenderUtils;
 import blade.addon.utils.rendering.RenderingEvents;
 import blade.addon.utils.times.PersonalBests;
+import com.mojang.blaze3d.vertex.PoseStack;
+import com.mojang.blaze3d.vertex.VertexConsumer;
 import config.practical.hud.HUDComponent;
 import net.fabricmc.fabric.api.client.event.lifecycle.v1.ClientTickEvents;
 import net.fabricmc.fabric.api.client.rendering.v1.world.WorldRenderContext;
-import net.minecraft.client.MinecraftClient;
-import net.minecraft.client.gui.DrawContext;
-import net.minecraft.client.render.DrawStyle;
-import net.minecraft.client.render.VertexConsumer;
-import net.minecraft.client.render.VertexRendering;
-import net.minecraft.client.util.math.MatrixStack;
-import net.minecraft.entity.Entity;
-import net.minecraft.entity.decoration.ArmorStandEntity;
-import net.minecraft.item.ItemStack;
-import net.minecraft.text.MutableText;
-import net.minecraft.text.Text;
-import net.minecraft.util.Formatting;
-import net.minecraft.util.hit.BlockHitResult;
-import net.minecraft.util.math.BlockPos;
-import net.minecraft.util.math.Box;
-import net.minecraft.util.math.ColorHelper;
-import net.minecraft.world.debug.gizmo.GizmoDrawing;
-
+import net.minecraft.ChatFormatting;
+import net.minecraft.client.Minecraft;
+import net.minecraft.client.gui.GuiGraphics;
+import net.minecraft.core.BlockPos;
+import net.minecraft.gizmos.GizmoStyle;
+import net.minecraft.gizmos.Gizmos;
+import net.minecraft.network.chat.Component;
+import net.minecraft.network.chat.MutableComponent;
+import net.minecraft.util.ARGB;
+import net.minecraft.world.entity.Entity;
+import net.minecraft.world.entity.decoration.ArmorStand;
+import net.minecraft.world.item.ItemStack;
+import net.minecraft.world.phys.AABB;
+import net.minecraft.world.phys.BlockHitResult;
 import java.util.regex.Matcher;
 import java.util.regex.Pattern;
 
 public class RelicTimer {
 
     public enum Relic {
-        GREEN(new Box(49, 7, 44, 50, 8, 45), 0xff55ff55, "§cRed"),
-        RED(new Box(51, 7, 42, 52, 8, 43), 0xffff5555, "§aGreen"),
-        PURPLE(new Box(54, 7, 41, 55, 8, 42), 0xffff55ff, "§5Purple"),
-        ORANGE(new Box(57, 7, 42, 58, 8, 43), 0xffffaa00, "§6Orange"),
-        BLUE(new Box(59, 7, 44, 60, 8, 45), 0xff55ffff, "§bBlue");
+        GREEN(new AABB(49, 7, 44, 50, 8, 45), 0xff55ff55, "§cRed"),
+        RED(new AABB(51, 7, 42, 52, 8, 43), 0xffff5555, "§aGreen"),
+        PURPLE(new AABB(54, 7, 41, 55, 8, 42), 0xffff55ff, "§5Purple"),
+        ORANGE(new AABB(57, 7, 42, 58, 8, 43), 0xffffaa00, "§6Orange"),
+        BLUE(new AABB(59, 7, 44, 60, 8, 45), 0xff55ffff, "§bBlue");
 
-        final Box box;
+        final AABB box;
         final int color;
         final String name;
         int placedTick = 0;
 
-        Relic(Box box, int color, String name) {
+        Relic(AABB box, int color, String name) {
             this.box = box;
             this.color = color;
             this.name = name;
@@ -117,7 +115,7 @@ public class RelicTimer {
 
                 if (EntityUtil.isClientPlayer(name)) {
                     pickedupRelic = Relic.valueOf(relicString.toUpperCase());
-                    Debug.sendDebugMessage(Text.literal("Picked up relic " + relicString));
+                    Debug.sendDebugMessage(Component.literal("Picked up relic " + relicString));
                 }
             }
 
@@ -127,15 +125,15 @@ public class RelicTimer {
         ClientTickEvents.END_WORLD_TICK.register(world -> {
             if (!Location.inDungeon() || !Phase.inP5() || !Floor7.showAllRelicTimes || sentRelicTimes) return;
 
-            Iterable<Entity> entities = world.getEntities();
+            Iterable<Entity> entities = world.entitiesForRendering();
 
             for (Entity entity : entities) {
-                if (entity instanceof ArmorStandEntity armorStand) {
-                    ItemStack item = armorStand.getWeaponStack();
+                if (entity instanceof ArmorStand armorStand) {
+                    ItemStack item = armorStand.getWeaponItem();
                     String id = ItemUtil.getId(item);
 
                     if (id == null || !id.contains("RELIC")) continue;
-                    Debug.sendDebugMessage(Text.literal("item id is " + id));
+                    Debug.sendDebugMessage(Component.literal("item id is " + id));
 
                     double ax = armorStand.getX();
                     double az = armorStand.getZ();
@@ -143,7 +141,7 @@ public class RelicTimer {
                     for (Relic relic : Relic.values()) {
                         if (relic.placedTick != 0 && Misc.getDistance(relic.box.maxX, relic.box.maxZ, ax, az) < 1.5) {
                             relic.placedTick = placeTick;
-                            Debug.sendDebugMessage(Text.literal("Set" + relic.name + " time to " + relic.placedTick + ", item id is " + id));
+                            Debug.sendDebugMessage(Component.literal("Set" + relic.name + " time to " + relic.placedTick + ", item id is " + id));
                         }
                     }
                 }
@@ -151,7 +149,7 @@ public class RelicTimer {
 
             if (allRelicsPlaced()) {
                 for (Relic relic : Relic.values()) {
-                    Misc.addChatMessage(Text.literal(relic.name + " &aRelic placed in &e" + TextUtil.formatTicks(relic.placedTick) + "s&a."));
+                    Misc.addChatMessage(Component.literal(relic.name + " &aRelic placed in &e" + TextUtil.formatTicks(relic.placedTick) + "s&a."));
                     sentRelicTimes = true;
                 }
             }
@@ -175,8 +173,8 @@ public class RelicTimer {
         //skyblock menu check is there incase the item doesnt get updated
         if (!ItemUtil.itemHasName(itemStack, "Relic") && !ItemUtil.itemHasName(itemStack, "Skyblock Menu")) {
             if (Floor7.blockIncorrectRelicPlace) {
-                Debug.sendDebugMessage(Text.literal("Name: " + itemStack.getName() + " Item: " + itemStack));
-                Misc.addChatMessage(Text.literal("Blocked a weird click"));
+                Debug.sendDebugMessage(Component.literal("Name: " + itemStack.getHoverName() + " Item: " + itemStack));
+                Misc.addChatMessage(Component.literal("Blocked a weird click"));
                 return true;
             } else {
                 return false;
@@ -185,9 +183,9 @@ public class RelicTimer {
 
         if (clickedRelic(pickedupRelic, pos)) {
             if (Floor7.enableRelicPlaceTime) {
-                MutableText text = Text.literal("The ").formatted(Formatting.GREEN)
-                        .append(Text.literal(pickedupRelic.name().toLowerCase()).withColor(pickedupRelic.color))
-                        .append(" relic was placed in ").formatted(Formatting.GREEN);
+                MutableComponent text = Component.literal("The 
…… diff 过长已截断 ……
- readded copy chatGabriel Ellnebrand · 2026-04-19 · 4d1c1d0
查看变更
src/main/java/blade/addon/mixin/ChatHudAccessor.java |  26 ++++++++++++++++++++
 src/main/java/blade/addon/mixin/ChatScreenMixin.java | 134 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 src/main/resources/blade-addons.mixins.json          |   2 ++
 3 files changed, 162 insertions(+)
diff --git a/src/main/java/blade/addon/mixin/ChatHudAccessor.java b/src/main/java/blade/addon/mixin/ChatHudAccessor.java
new file mode 100644
index 0000000..a0e4345
--- /dev/null
+++ b/src/main/java/blade/addon/mixin/ChatHudAccessor.java
@@ -0,0 +1,26 @@
+package blade.addon.mixin;
+
+import net.minecraft.client.gui.hud.ChatHud;
+import net.minecraft.client.gui.hud.ChatHudLine;
+import org.spongepowered.asm.mixin.Mixin;
+import org.spongepowered.asm.mixin.gen.Accessor;
+import org.spongepowered.asm.mixin.gen.Invoker;
+
+import java.util.List;
+
+@Mixin(ChatHud.class)
+public interface ChatHudAccessor {
+
+    @Accessor("visibleMessages")
+    List<ChatHudLine.Visible> getVisibleMessages();
+
+    @Accessor("scrolledLines")
+    int getScrolledLines();
+
+    @Invoker("getWidth")
+    int width();
+
+    @Invoker("getChatScale")
+    double getScale();
+
+}
diff --git a/src/main/java/blade/addon/mixin/ChatScreenMixin.java b/src/main/java/blade/addon/mixin/ChatScreenMixin.java
new file mode 100644
index 0000000..d15e4e2
--- /dev/null
+++ b/src/main/java/blade/addon/mixin/ChatScreenMixin.java
@@ -0,0 +1,134 @@
+package blade.addon.mixin;
+
+import blade.addon.utils.Misc;
+import blade.addon.utils.config.values.ExtraOptions;
+import blade.addon.utils.data.TextUtil;
+import net.minecraft.client.MinecraftClient;
+import net.minecraft.client.gui.Click;
+import net.minecraft.client.gui.hud.ChatHud;
+import net.minecraft.client.gui.hud.ChatHudLine;
+import net.minecraft.client.gui.screen.ChatScreen;
+import net.minecraft.text.Text;
+import net.minecraft.util.math.MathHelper;
+import org.lwjgl.glfw.GLFW;
+import org.spongepowered.asm.mixin.Mixin;
+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.callback.CallbackInfoReturnable;
+
+import java.util.List;
+
+@Mixin(ChatScreen.class)
+public class ChatScreenMixin {
+
+    @Inject(method = "mouseClicked", at = @At("HEAD"))
+    private void click(Click click, boolean doubled, CallbackInfoReturnable<Boolean> cir) {
+
+        if (!ExtraOptions.copyChat || click.button() != GLFW.GLFW_MOUSE_BUTTON_RIGHT) return;
+
+        MinecraftClient mc = MinecraftClient.getInstance();
+        ChatHud chatHud = mc.inGameHud.getChatHud();
+        ChatHudAccessor hudAccessor = (ChatHudAccessor) chatHud;
+        if (chatHud == null) return;
+
+        if (hudAccessor.width() + 4 < click.x()) return;
+
+        final int n = (int) (9 * (mc.options.getChatLineSpacing().getValue() + 1.0));
+        int index = (int) (MathHelper.floor((mc.getWindow().getScaledHeight() - 40) / hudAccessor.getScale()) - click.y()) / n + hudAccessor.getScrolledLines();
+
+        List<ChatHudLine.Visible> messages = hudAccessor.getVisibleMessages();
+        if (index < 0 || index >= messages.size()) return;
+
+        String string;
+        if (ExtraOptions.copyLineOnly) {
+            ChatHudLine.Visible msg = messages.get(index);
+            string = TextUtil.orderedTextToString(msg.content());
+        } else {
+            string = copyChat(messages, index);
+        }
+
+        if (string == null) return;
+
+        if (ExtraOptions.removeColorCodes) {
+            string = string.replaceAll("§.", "");
+        }
+
+        if (ExtraOptions.replaceColorChars) {
+            string = string.replace("§", "&");
+        }
+
+        mc.keyboard.setClipboard(string);
+
+        if (ExtraOptions.copyChatFeedback) {
+            Misc.addChatMessage(Text.literal("Copied chat message"));
+        }
+
+    }
+
+    @Unique
+    private static String copyChat(List<ChatHudLine.Visible> messages, int index) {
+        int endIndex = index;
+
+        if (messages == null || endIndex < 0 || endIndex >= messages.size()) return null;
+
+        int startIndex = endIndex;
+
+
+        //edge case where the index is end of entry
+        //but there are lines above that needs to be included
+        ChatHudLine.Visible currentLine = messages.get(endIndex);
+        if (endIndex + 1 < messages.size() && currentLine.endOfEntry() && !messages.get(startIndex + 1).endOfEntry()) {
+            endIndex++;
+        }
+
+
+        //find start of msg
+        for (int i = endIndex; i >= 0; i--) {
+            ChatHudLine.Visible chatHudLine = messages.get(i);
+            if (chatHudLine.endOfEntry()) {
+                startIndex = i;
+                break;
+            }
+        }
+
+        if (!messages.get(endIndex).endOfEntry()) {
+            //find end of msg
+            for (int i = endIndex + 1; i < messages.size(); i++) {
+                ChatHudLine.Visible chatHudLine = messages.get(i);
+                if (chatHudLine.endOfEntry()) {
+                    endIndex = i - 1;
+                    break;
+                }
+            }
+        }
+        StringBuilder builder = new StringBuilder();
+        for (int i = endIndex; i >= startIndex; i--) {
+            ChatHudLine.Visible chatHudLine = messages.get(i);
+            TextUtil.acceptOrderedText(builder, chatHudLine.content());
+        }
+        return builder.toString();
+    }
+
+
+    //final int l = 9;
+    //double d = this.client.options.getChatLineSpacing().getValue();
+    //final int k = MathHelper.floor((windowHeight - 40) / f);
+    //final int n = (int)(l * (d + 1.0));
+    //int lx = k - y * n;
+    //float f = (float)this.getChatScale();
+
+    //
+    //
+    //MATH PART
+    //mx <= click.y <= lx
+    //lx + y * n = k
+    //(k - lx) / n = y = forEachVisibleLine.k
+    //lineIndex = forEachVisibleLine.l = forEachVisibleLine.k + this.scrolledLines
+
+}
+
+
+
+
+
diff --git a/src/main/resources/blade-addons.mixins.json b/src/main/resources/blade-addons.mixins.json
index a6b74a3..9e9f49c 100644
--- a/src/main/resources/blade-addons.mixins.json
+++ b/src/main/resources/blade-addons.mixins.json
@@ -22,6 +22,8 @@
   },
   "client": [
     "BossBarHudMixin",
+    "ChatHudAccessor",
+    "ChatScreenMixin",
     "ClientPlayerEntityMixin",
     "ClientPlayNetworkHandlerMixin",
     "DrawContextMixin",
forgot to push theseGabriel Ellnebrand · 2026-04-19 · 1a42501
查看变更
src/main/java/blade/addon/features/highlight/BatHighlight.java      |  4 ++--
 src/main/java/blade/addon/features/highlight/MimicHighlight.java    |  4 ++--
 src/main/java/blade/addon/features/highlight/MobHighlight.java      |  4 ++--
 src/main/java/blade/addon/features/highlight/SheepHighlight.java    |  4 ++--
 src/main/java/blade/addon/features/highlight/TeammateHighlight.java |  2 +-
 src/main/java/blade/addon/features/highlight/WitherHighlight.java   |  4 ++--
 src/main/java/blade/addon/features/other/PracticeSS.java            | 14 +++++++-------
 src/main/java/blade/addon/features/other/StunWaypoint.java          | 62 --------------------------------------------------------------
 8 files changed, 18 insertions(+), 80 deletions(-)
diff --git a/src/main/java/blade/addon/features/highlight/BatHighlight.java b/src/main/java/blade/addon/features/highlight/BatHighlight.java
index 12b72dc..046fa71 100644
--- a/src/main/java/blade/addon/features/highlight/BatHighlight.java
+++ b/src/main/java/blade/addon/features/highlight/BatHighlight.java
@@ -48,14 +48,14 @@ public class BatHighlight {
         if (!MobHighlight.mobHighlight || !MobHighlight.renderFilled()) return;
 
         float[] rgba = RenderUtils.toFloats(MobHighlight.batFilledColor);
-        bats.forEach(entity -> RenderUtils.renderFilled(matrixStack, consumer, EntityUtil.getBox(entity), rgba));
+        bats.forEach(entity -> RenderUtils.renderFilled(EntityUtil.getBox(entity), rgba));
     }
 
     private static void renderOutline(WorldRenderContext context, MatrixStack matrixStack, VertexConsumer consumer) {
         if (!MobHighlight.mobHighlight || !MobHighlight.renderOutline()) return;
 
         float[] rgba = RenderUtils.toFloats(MobHighlight.batOutlineColor);
-        bats.forEach(entity -> RenderUtils.renderOutline(matrixStack, consumer, EntityUtil.getBox(entity), rgba));
+        bats.forEach(entity -> RenderUtils.renderOutline(EntityUtil.getBox(entity), rgba));
     }
 
 }
diff --git a/src/main/java/blade/addon/features/highlight/MimicHighlight.java b/src/main/java/blade/addon/features/highlight/MimicHighlight.java
index 3880a0f..f7a1892 100644
--- a/src/main/java/blade/addon/features/highlight/MimicHighlight.java
+++ b/src/main/java/blade/addon/features/highlight/MimicHighlight.java
@@ -46,13 +46,13 @@ public class MimicHighlight {
         if (!MobHighlight.highlightMimicChests || !MobHighlight.renderFilled()) return;
 
         float[] rgba = RenderUtils.toFloats(MobHighlight.mimicFilledColor);
-        mimics.forEach(mimic -> RenderUtils.renderFilled(matrixStack, consumer, box.offset(mimic.getPos()), rgba));
+        mimics.forEach(mimic -> RenderUtils.renderFilled(box.offset(mimic.getPos()), rgba));
     }
 
     private static void renderOutline(WorldRenderContext context, MatrixStack matrixStack, VertexConsumer consumer) {
         if (!MobHighlight.highlightMimicChests || !MobHighlight.renderOutline()) return;
 
         float[] rgba = RenderUtils.toFloats(MobHighlight.mimicOutlineColor);
-        mimics.forEach(mimic -> RenderUtils.renderOutline(matrixStack, consumer, box.offset(mimic.getPos()), rgba));
+        mimics.forEach(mimic -> RenderUtils.renderOutline(box.offset(mimic.getPos()), rgba));
     }
 }
diff --git a/src/main/java/blade/addon/features/highlight/MobHighlight.java b/src/main/java/blade/addon/features/highlight/MobHighlight.java
index a4efecc..4122947 100644
--- a/src/main/java/blade/addon/features/highlight/MobHighlight.java
+++ b/src/main/java/blade/addon/features/highlight/MobHighlight.java
@@ -315,7 +315,7 @@ public class MobHighlight {
             int filledColor = getFilledColor(type);
             float[] rgba = RenderUtils.toFloats(filledColor);
 
-            RenderUtils.renderFilled(matrixStack, consumer, box, rgba);
+            RenderUtils.renderFilled(box, rgba);
 
         }
     }
@@ -333,7 +333,7 @@ public class MobHighlight {
             int outlineColor = getOutlineColor(type);
             float[] rgba = RenderUtils.toFloats(outlineColor);
 
-            RenderUtils.renderOutline(matrixStack, consumer, box, rgba);
+            RenderUtils.renderOutline(box, rgba);
         }
     }
 }
diff --git a/src/main/java/blade/addon/features/highlight/SheepHighlight.java b/src/main/java/blade/addon/features/highlight/SheepHighlight.java
index cadf740..14441db 100644
--- a/src/main/java/blade/addon/features/highlight/SheepHighlight.java
+++ b/src/main/java/blade/addon/features/highlight/SheepHighlight.java
@@ -42,14 +42,14 @@ public class SheepHighlight {
         if (!MobHighlight.highlightSheep || !MobHighlight.renderFilled()) return;
 
         float[] rgba = RenderUtils.toFloats(MobHighlight.sheepFilledColor);
-        sheeps.forEach(entity -> RenderUtils.renderFilled(matrixStack, consumer, EntityUtil.getBox(entity), rgba));
+        sheeps.forEach(entity -> RenderUtils.renderFilled(EntityUtil.getBox(entity), rgba));
     }
 
     private static void renderOutline(WorldRenderContext context, MatrixStack matrixStack, VertexConsumer consumer) {
         if (!MobHighlight.highlightSheep || !MobHighlight.renderOutline()) return;
 
         float[] rgba = RenderUtils.toFloats(MobHighlight.sheepFilledColor);
-        sheeps.forEach(entity -> RenderUtils.renderOutline(matrixStack, consumer, EntityUtil.getBox(entity), rgba));
+        sheeps.forEach(entity -> RenderUtils.renderOutline(EntityUtil.getBox(entity), rgba));
     }
 
 }
diff --git a/src/main/java/blade/addon/features/highlight/TeammateHighlight.java b/src/main/java/blade/addon/features/highlight/TeammateHighlight.java
index e251712..2703c6d 100644
--- a/src/main/java/blade/addon/features/highlight/TeammateHighlight.java
+++ b/src/main/java/blade/addon/features/highlight/TeammateHighlight.java
@@ -58,7 +58,7 @@ public class TeammateHighlight {
 
             if (Dungeons.highlightTeammates) {
                 float[] rgba = RenderUtils.toFloats(color);
-                RenderUtils.renderOutline(matrixStack, consumer, EntityUtil.getBox(player), rgba);
+                RenderUtils.renderOutline(EntityUtil.getBox(player), rgba);
             }
 
             if (Dungeons.renderClassName) {
diff --git a/src/main/java/blade/addon/features/highlight/WitherHighlight.java b/src/main/java/blade/addon/features/highlight/WitherHighlight.java
index 01cfb1c..4e3b9a2 100644
--- a/src/main/java/blade/addon/features/highlight/WitherHighlight.java
+++ b/src/main/java/blade/addon/features/highlight/WitherHighlight.java
@@ -56,14 +56,14 @@ public class WitherHighlight {
         if (!MobHighlight.mobHighlight || !MobHighlight.renderFilled() || MobHighlight.dontRenderHighlight) return;
 
         float[] rgba = RenderUtils.toFloats(MobHighlight.witherFilledColor);
-        withers.forEach(entity -> RenderUtils.renderFilled(matrixStack, consumer, getBox(entity), rgba));
+        withers.forEach(entity -> RenderUtils.renderFilled(getBox(entity), rgba));
     }
 
     private static void renderOutline(WorldRenderContext context, MatrixStack matrixStack, VertexConsumer consumer) {
         if (!MobHighlight.mobHighlight || !MobHighlight.renderOutline() || MobHighlight.dontRenderHighlight) return;
 
         float[] rgba = RenderUtils.toFloats(MobHighlight.witherOutlineColor);
-        withers.forEach(entity -> RenderUtils.renderOutline(matrixStack, consumer, getBox(entity), rgba));
+        withers.forEach(entity -> RenderUtils.renderOutline(getBox(entity), rgba));
     }
 
 }
diff --git a/src/main/java/blade/addon/features/other/PracticeSS.java b/src/main/java/blade/addon/features/other/PracticeSS.java
index 4380ae4..8d4d1be 100644
--- a/src/main/java/blade/addon/features/other/PracticeSS.java
+++ b/src/main/java/blade/addon/features/other/PracticeSS.java
@@ -353,15 +353,15 @@ public class PracticeSS {
         if (showingPattern) {
             if (!ExtraOptions.realisticDelay || totalTicks - ticksLeft > START_WAIT_DURATION || endIndex != 2) {
                 int lastIndex = getLastDisplayIndex();
-                renderButtons(matrixStack, consumer, 0, lastIndex);
-                renderBackground(matrixStack, consumer, lastIndex - 1);
+                renderButtons(0, lastIndex);
+                renderBackground(lastIndex - 1);
             }
         } else {
-            renderButtons(matrixStack, consumer, currentIndex, endIndex);
+            renderButtons(currentIndex, endIndex);
         }
     }
 
-    private static void renderButtons(MatrixStack matrixStack, VertexConsumer consumer, int start, int end) {
+    private static void renderButtons(int start, int end) {
         Box directionBox = getBox(direction);
         if (directionBox == null) return;
 
@@ -370,7 +370,7 @@ public class PracticeSS {
             BlockPos pos = buttons.get(i);
             Box box = directionBox.offset(pos.getX(), pos.getY(), pos.getZ());
             float[] color = RenderUtils.toFloats(getColor(i));
-            RenderUtils.renderFilled(matrixStack, consumer, box, color);
+            RenderUtils.renderFilled(box, color);
         }
     }
 
@@ -384,7 +384,7 @@ public class PracticeSS {
         };
     }
 
-    private static void renderBackground(MatrixStack matrixStack, VertexConsumer consumer, int backgroundIndex) {
+    private static void renderBackground(int backgroundIndex) {
         if (buttons.size() > backgroundIndex && backgroundIndex >= 0) {
             Box box = Box.of(buttons.get(backgroundIndex).toCenterPos(), 1, 1, 1);
             int dx, dz;
@@ -409,7 +409,7 @@ public class PracticeSS {
                     return;
                 }
             }
-            RenderUtils.renderFilled(matrixStack, consumer, box.offset(dx, 0, dz), new float[]{0, 0.5f, 1, 1});
+            RenderUtils.renderFilled(box.offset(dx, 0, dz), new float[]{0, 0.5f, 1, 1});
         }
     }
 }
diff --git a/src/main/java/blade/addon/features/other/StunWaypoint.java b/src/main/java/blade/addon/features/other/StunWaypoint.java
deleted file mode 100644
index 742152b..0000000
--- a/src/main/java/blade/addon/features/other/StunWaypoint.java
+++ /dev/null
@@ -1,62 +0,0 @@
-package blade.addon.features.other;
-
-import blade.addon.utils.Location;
-import blade.addon.utils.config.values.ExtraOptions;
-import blade.addon.utils.events.Events;
-import blade.addon.utils.rendering.RenderUtils;
-import blade.addon.utils.rendering.RenderingEvents;
-import net.minecraft.network.packet.s2c.play.PlayerPositionLookS2CPacket;
-import net.minecraft.util.math.Box;
-import net.minecraft.util.math.Vec3d;
-
-public class StunWaypoint {
-
-    private static final Box BLOCK = new Box(-168, 27, -169, -167, 28, -168);
-
-    private static boolean inBelly = false;
-    private static boolean inP3 = false;
-
-    public static void init() {
-
-        Events.ON_GAME_MESSAGE.register(text -> {
-            if (!Location.in(Location.KUUDRA) || !ExtraOptions.stunWaypoint) return false;
-            String string = text.getString();
-
-            if (string.equals("[NPC] Elle: Phew! The Ballista is finally ready! It should be strong enough to tank Kuudra's blows now!")) {
-                inP3 = true;
-                return false;
-            }
-
-            if (string.equals("[NPC] Elle: POW! SURELY THAT'S IT! I don't think he has any more in him!")) {
-                reset();
-                return false;
-            }
-
-            return false;
-        });
-
-        Events.ON_PACKET.register(packet -> {
-            if (!Location.in(Location.KUUDRA) || !ExtraOptions.stunWaypoint) return false;
-
-            if (packet instanceof PlayerPositionLookS2CPacket posPacket) {
-                Vec3d pos = posPacket.change().position();
-                if (pos.x == -161 && pos.y == 49 && pos.z == -186) {
-                    inBelly = true;
-                }
-
-            }
-            return false;
-        });
-
-        RenderingEvents.NO_DEPTH_OUTLINE_ENTITY.register((context, matrixStack, consumer) -> {
-            if (!inP3 || !inBelly || !Location.in(Location.KUUDRA) || !ExtraOptions.stunWaypoint) return;
-            RenderUtils.renderOutline(matrixStack, consumer, BLOCK, new float[]{0, 1, 1, 1});
-        });
-    }
-
-
-    private static void reset() {
-        inBelly = false;
-        inP3 = false;
-    }
-}
- fixed renderLineTo(tracer) not working correctly - refactored some rendering methods to use the new rendering methods - removed "StunWaypoint.java", not needed as it was never tested or accessible and all kuudra mods have already ported anywayGabriel Ellnebrand · 2026-04-19 · d8012e6
查看变更
src/main/java/blade/addon/Bladeaddons.java                              | 47 +++++------------------------------------------
 src/main/java/blade/addon/features/dungeon/f7/dragons/DragonTracer.java |  2 +-
 src/main/java/blade/addon/utils/rendering/RenderUtils.java              | 20 ++++++++------------
 3 files changed, 14 insertions(+), 55 deletions(-)
diff --git a/src/main/java/blade/addon/Bladeaddons.java b/src/main/java/blade/addon/Bladeaddons.java
index 79fc730..16ce65e 100644
--- a/src/main/java/blade/addon/Bladeaddons.java
+++ b/src/main/java/blade/addon/Bladeaddons.java
@@ -1,17 +1,6 @@
 package blade.addon;
 
-import blade.addon.features.dungeon.AutoRequeue;
-import blade.addon.features.dungeon.BloodNotifier;
-import blade.addon.features.dungeon.ChestCounter;
-import blade.addon.features.dungeon.ExplosiveShot;
-import blade.addon.features.dungeon.HidePlayers;
-import blade.addon.features.dungeon.ItemHighlight;
-import blade.addon.features.dungeon.KeyNotifier;
-import blade.addon.features.dungeon.LeapMessage;
-import blade.addon.features.dungeon.QuizTimer;
-import blade.addon.features.dungeon.RunStartValidator;
-import blade.addon.features.dungeon.SecretSpawnTimer;
-import blade.addon.features.dungeon.WarpCooldown;
+import blade.addon.features.dungeon.*;
 import blade.addon.features.dungeon.f7.BossWaypoints;
 import blade.addon.features.dungeon.f7.PredevTimer;
 import blade.addon.features.dungeon.f7.RelicTimer;
@@ -29,37 +18,12 @@ import blade.addon.features.dungeon.f7.maxor.MaxorTickTimer;
 import blade.addon.features.dungeon.f7.storm.DistanceToLedge;
 import blade.addon.features.dungeon.f7.storm.PillarExplode;
 import blade.addon.features.dungeon.f7.storm.StormTickTimer;
-import blade.addon.features.dungeon.f7.terms.CurrentSection;
-import blade.addon.features.dungeon.f7.terms.DeviceNotifier;
-import blade.addon.features.dungeon.f7.terms.GoldorTickTimer;
-import blade.addon.features.dungeon.f7.terms.LeapNotification;
-import blade.addon.features.dungeon.f7.terms.MelodyWarning;
-import blade.addon.features.dungeon.f7.terms.SectionCompletion;
-import blade.addon.features.dungeon.f7.terms.SectionProgress;
-import blade.addon.features.dungeon.f7.terms.TermStartTimer;
+import blade.addon.features.dungeon.f7.terms.*;
 import blade.addon.features.filter.Filters;
-import blade.addon.features.highlight.BatHighlight;
-import blade.addon.features.highlight.MimicHighlight;
-import blade.addon.features.highlight.MobHighlight;
-import blade.addon.features.highlight.SheepHighlight;
-import blade.addon.features.highlight.TeammateHighlight;
-import blade.addon.features.highlight.WitherHighlight;
-import blade.addon.features.item.DropAnimation;
-import blade.addon.features.item.ItemRarityHighlight;
-import blade.addon.features.item.ProtectItem;
-import blade.addon.features.item.SelectedPetHighlight;
-import blade.addon.features.item.StarCountHighlight;
+import blade.addon.features.highlight.*;
+import blade.addon.features.item.*;
 import blade.addon.features.notifications.Notifications;
-import blade.addon.features.other.ArrowSwapper;
-import blade.addon.features.other.CompactHoppity;
-import blade.addon.features.other.DianaNotifier;
-import blade.addon.features.other.KickedTimer;
-import blade.addon.features.other.PracticeSS;
-import blade.addon.features.other.RagDisplay;
-import blade.addon.features.other.ReaperDisplay;
-import blade.addon.features.other.SearchBar;
-import blade.addon.features.other.SelectedPet;
-import blade.addon.features.other.StunWaypoint;
+import blade.addon.features.other.*;
 import blade.addon.features.sound.BonzoSound;
 import blade.addon.features.sound.TubaSound;
 import blade.addon.utils.Commands;
@@ -152,7 +116,6 @@ public class Bladeaddons implements ModInitializer {
         ArrowSwapper.init();
         StarCountHighlight.init();
         MaskHighlight.init();
-        StunWaypoint.init();
         ItemRarityHighlight.init();
         ProtectItem.init();
         SelectedPetHighlight.init();
diff --git a/src/main/java/blade/addon/features/dungeon/f7/dragons/DragonTracer.java b/src/main/java/blade/addon/features/dungeon/f7/dragons/DragonTracer.java
index d63028b..aace71b 100644
--- a/src/main/java/blade/addon/features/dungeon/f7/dragons/DragonTracer.java
+++ b/src/main/java/blade/addon/features/dungeon/f7/dragons/DragonTracer.java
@@ -19,6 +19,6 @@ public class DragonTracer {
         Dragon dragon = DragSpawnTimer.currentDragon;
         if (dragon == null || dragon == Dragon.NONE || !DragSpawnTimer.hasDoneSplit) return;
 
-        RenderUtils.renderLineTo(context, matrixStack, consumer, dragon.spawnPos, dragon.color);
+        RenderUtils.renderLineTo(context, dragon.spawnPos, dragon.color);
     }
 }
diff --git a/src/main/java/blade/addon/utils/rendering/RenderUtils.java b/src/main/java/blade/addon/utils/rendering/RenderUtils.java
index 8d5056c..9822eea 100644
--- a/src/main/java/blade/addon/utils/rendering/RenderUtils.java
+++ b/src/main/java/blade/addon/utils/rendering/RenderUtils.java
@@ -11,7 +11,6 @@ import net.minecraft.client.font.TextRenderer;
 import net.minecraft.client.gui.DrawContext;
 import net.minecraft.client.network.ClientPlayerEntity;
 import net.minecraft.client.render.DrawStyle;
-import net.minecraft.client.render.VertexConsumer;
 import net.minecraft.client.util.math.MatrixStack;
 import net.minecraft.text.Text;
 import net.minecraft.util.math.Box;
@@ -89,13 +88,13 @@ public class RenderUtils {
         context.drawText(textRenderer, drawnText, component.getScaledX(), component.getScaledY(), 0xffffffff, true);
     }
 
-    public static void renderFilled(MatrixStack matrixStack, VertexConsumer consumer, Box box, float[] rgba) {
+    public static void renderFilled(Box box, float[] rgba) {
         if (rgba[3] == 0) return;
         GizmoDrawing.box(box, DrawStyle.filled(ColorHelper.fromFloats(rgba[3], rgba[0], rgba[1], rgba[2]))); // Could be filledAndStroked
         //VertexRendering.drawFilledBox(matrixStack, consumer, box.minX, box.minY, box.minZ, box.maxX, box.maxY, box.maxZ, rgba[0], rgba[1], rgba[2], rgba[3]);
     }
 
-    public static void renderOutline(MatrixStack matrixStack, VertexConsumer consumer, Box box, float[] rgba) {
+    public static void renderOutline(Box box, float[] rgba) {
         if (rgba[3] == 0) return;
         GizmoDrawing.box(box, DrawStyle.stroked(ColorHelper.fromFloats(rgba[3], rgba[0], rgba[1], rgba[2]))); // Could be filledAndStroked
         //VertexRendering.drawBox(matrixStack.peek(), consumer, box, rgba[0], rgba[1], rgba[2], rgba[3]);
@@ -123,22 +122,19 @@ public class RenderUtils {
         renderText(context, matrices, text, pos.x, pos.y, pos.z, scale);
     }
 
-    public static void renderLineTo(WorldRenderContext context, MatrixStack matrices, VertexConsumer consumer, double x, double y, double z, int color) {
+    public static void renderLineTo(WorldRenderContext context, double x, double y, double z, int color) {
         ClientPlayerEntity player = MinecraftClient.getInstance().player;
         if (player == null) return;
 
         Vec3d playerPos = EntityUtil.getLerpedPos(player);
         double eyeHeight = player.getStandingEyeHeight();
-        Vector3f lookat = new Vector3f(0, 0, -1f).rotate(context.worldState().cameraRenderState.orientation);
-        Vec3d startPos = playerPos.add(0, eyeHeight, 0).add(lookat.x, lookat.y + eyeHeight, lookat.z);
-        Vec3d endPos = new Vec3d(x, y, z).subtract(playerPos).subtract(lookat.x, lookat.y + eyeHeight, lookat.z);
-
-        //VertexRendering.drawVector(matrices, consumer, startPos, endPos, color);
-        GizmoDrawing.line(startPos, endPos, color);
+        Vector3f lookAt = new Vector3f(0, 0, -1f).rotate(context.worldState().cameraRenderState.orientation);
+        Vec3d startPos = playerPos.add(0, eyeHeight, 0).add(lookAt.x, lookAt.y, lookAt.z);
+        GizmoDrawing.line(startPos, new Vec3d(x, y, z), color);
     }
 
-    public static void renderLineTo(WorldRenderContext context, MatrixStack matrices, VertexConsumer consumer, Vec3d pos, int color) {
-        renderLineTo(context, matrices, consumer, pos.x, pos.y, pos.z, color);
+    public static void renderLineTo(WorldRenderContext context, Vec3d pos, int color) {
+        renderLineTo(context, pos.x, pos.y, pos.z, color);
     }
 
         public static String formatNumber(float num) {
- should actually launch on 1.21.11 now - fixed heldItemRenderer - possibly fixed tracers - copy chat still missingGabriel Ellnebrand · 2026-03-23 · eb4b15b
查看变更
build.gradle                                                   |   2 +-
 gradle.properties                                              |   4 ++--
 src/main/java/blade/addon/features/other/SwingAnimation.java   |  15 ++++++++++++-
 src/main/java/blade/addon/mixin/ChatHudInvoker.java            |  25 ----------------------
 src/main/java/blade/addon/mixin/ChatScreenMixin.java           | 103 -----------------------------------------------------------------------------------------
 src/main/java/blade/addon/mixin/HeldItemRendererMixin.java     |  27 +++++++++++++-----------
 src/main/java/blade/addon/utils/rendering/RenderUtils.java     |  12 +++++------
 src/main/java/blade/addon/utils/rendering/RenderingEvents.java |   2 ++
 src/main/resources/blade-addons.mixins.json                    |   2 --
 9 files changed, 40 insertions(+), 152 deletions(-)
diff --git a/build.gradle b/build.gradle
index 8003afc..afd5e5f 100644
--- a/build.gradle
+++ b/build.gradle
@@ -69,7 +69,7 @@ dependencies {
 	include modImplementation("net.hypixel:mod-api:${project.hypixel_api_version}")
 	include modImplementation("com.github.BladeMasterGabe:practical-config:${project.practical_config}")
 
-    modRuntimeOnly("me.djtheredstoner:DevAuth-fabric:1.2.1")
+    modRuntimeOnly("me.djtheredstoner:DevAuth-fabric:1.2.2")
 
 
 }
diff --git a/gradle.properties b/gradle.properties
index ce9672b..cd2d845 100644
--- a/gradle.properties
+++ b/gradle.properties
@@ -13,7 +13,7 @@ loader_version=0.18.4
 loom_version=1.15-SNAPSHOT
 
 # Mod Properties
-mod_version=2.3.6
+mod_version=2.4.0
 maven_group=blade.addon
 archives_base_name=blade-addons
 
@@ -21,5 +21,5 @@ archives_base_name=blade-addons
 fabric_version=0.141.3+1.21.11
 # I love dependencies sometimes breaking
 practical_config=1.4.3
-modmenu_version = 16.0.0-rc.2
+modmenu_version = 17.0.0-beta.2
 hypixel_api_version = 1.0.1
diff --git a/src/main/java/blade/addon/features/other/SwingAnimation.java b/src/main/java/blade/addon/features/other/SwingAnimation.java
index 4c21bf7..e816ebf 100644
--- a/src/main/java/blade/addon/features/other/SwingAnimation.java
+++ b/src/main/java/blade/addon/features/other/SwingAnimation.java
@@ -1,5 +1,8 @@
 package blade.addon.features.other;
 
+import blade.addon.features.item.DropAnimation;
+import net.minecraft.item.ItemStack;
+
 public class SwingAnimation {
 
     private static int ignoreCount = 0;
@@ -17,14 +20,24 @@ public class SwingAnimation {
     public static void consumeSwing() {
         if (ignoreCount > 0) {
             ignoreCount--;
+            System.out.println("Consumed a swing, now at count: " + ignoreCount);
         }
     }
 
     /**
      * Checks if it should ignore the swing
+     *
      * @return true if it should ignore the swing animation, else false
      */
     public static boolean shouldIgnore() {
-        return ignoreCount > 0;
+        if (ignoreCount > 0) {
+            return true;
+        }
+
+        if (DropAnimation.shouldProceed()) {
+            ItemStack prevDropped = DropAnimation.getPrevDroppedItem();
+            return prevDropped != null;
+        }
+        return false;
     }
 }
diff --git a/src/main/java/blade/addon/mixin/ChatHudInvoker.java b/src/main/java/blade/addon/mixin/ChatHudInvoker.java
deleted file mode 100644
index a931d24..0000000
--- a/src/main/java/blade/addon/mixin/ChatHudInvoker.java
+++ /dev/null
@@ -1,25 +0,0 @@
-package blade.addon.mixin;
-
-import net.minecraft.client.gui.hud.ChatHud;
-import net.minecraft.client.gui.hud.ChatHudLine;
-import org.spongepowered.asm.mixin.Mixin;
-import org.spongepowered.asm.mixin.gen.Accessor;
-import org.spongepowered.asm.mixin.gen.Invoker;
-
-import java.util.List;
-
-@Mixin(ChatHud.class)
-public interface ChatHudInvoker {
-
-    @Invoker("toChatLineY")
-    double lineY(double y);
-
-    @Invoker("toChatLineX")
-    double lineX(double X);
-
-    @Invoker("getMessageLineIndex")
-    int getLineIndex(double chatLineX, double chatLineY);
-
-    @Accessor("visibleMessages")
-    List<ChatHudLine.Visible> getVisibleMessages();
-}
diff --git a/src/main/java/blade/addon/mixin/ChatScreenMixin.java b/src/main/java/blade/addon/mixin/ChatScreenMixin.java
deleted file mode 100644
index 5e05d54..0000000
--- a/src/main/java/blade/addon/mixin/ChatScreenMixin.java
+++ /dev/null
@@ -1,103 +0,0 @@
-package blade.addon.mixin;
-
-import blade.addon.utils.Misc;
-import blade.addon.utils.config.values.ExtraOptions;
-import blade.addon.utils.data.TextUtil;
-import net.minecraft.client.MinecraftClient;
-import net.minecraft.client.gui.Click;
-import net.minecraft.client.gui.hud.ChatHudLine;
-import net.minecraft.client.gui.screen.ChatScreen;
-import net.minecraft.client.gui.screen.Screen;
-import net.minecraft.text.Text;
-import org.lwjgl.glfw.GLFW;
-import org.spongepowered.asm.mixin.Mixin;
-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.callback.CallbackInfoReturnable;
-
-import java.util.List;
-
-@Mixin(ChatScreen.class)
-public class ChatScreenMixin extends Screen {
-
-    protected ChatScreenMixin(Text title) {
-        super(title);
-    }
-
-    @Inject(method = "mouseClicked", at = @At("HEAD"))
-    private static void mouseClicked(Click click, boolean doubled, CallbackInfoReturnable<Boolean> cir) {
-        if (!ExtraOptions.copyChat || click.button() != GLFW.GLFW_MOUSE_BUTTON_RIGHT) return;
-
-        MinecraftClient mc = MinecraftClient.getInstance();
-        ChatHudInvoker hudInvoker = (ChatHudInvoker) mc.inGameHud.getChatHud();
-        if (hudInvoker == null) return;
-
-        double x = hudInvoker.lineX(click.x());
-        double y = hudInvoker.lineY(click.y());
-
-        String string;
-        if (ExtraOptions.copyLineOnly) {
-            int index = hudInvoker.getLineIndex(x, y);
-            List<ChatHudLine.Visible> visibleMessages = hudInvoker.getVisibleMessages();
-            if (visibleMessages == null || index < 0 || index >= visibleMessages.size()) return;
-
-            ChatHudLine.Visible msg = visibleMessages.get(index);
-            string = TextUtil.orderedTextToString(msg.content());
-        } else {
-            string = copyChat(hudInvoker, x, y);
-        }
-
-        if (string == null) return;
-
-        if (ExtraOptions.removeColorCodes) {
-            string = string.replaceAll("§.", "");
-        }
-
-        if (ExtraOptions.replaceColorChars) {
-            string = string.replaceAll("§", "&");
-        }
-
-        mc.keyboard.setClipboard(string);
-
-        if (ExtraOptions.copyChatFeedback) {
-            Misc.addChatMessage(Text.literal("Copied chat message"));
-        }
-    }
-
-    @Unique
-    private static String copyChat(ChatHudInvoker hudInvoker, double x, double y) {
-        List<ChatHudLine.Visible> messages = hudInvoker.getVisibleMessages();
-        int endIndex = hudInvoker.getLineIndex(x, y);
-
-        if (messages == null || endIndex < 0 || endIndex >= messages.size()) return null;
-
-        int startIndex = endIndex;
-
-        //find start of msg
-        for (int i = endIndex; i >= 0; i--) {
-            ChatHudLine.Visible chatHudLine = messages.get(i);
-            if (chatHudLine.endOfEntry()) {
-                startIndex = i;
-                break;
-            }
-        }
-
-        if (!messages.get(endIndex).endOfEntry()) {
-            //find end of msg
-            for (int i = endIndex + 1; i < messages.size(); i++) {
-                ChatHudLine.Visible chatHudLine = messages.get(i);
-                if (chatHudLine.endOfEntry()) {
-                    endIndex = i - 1;
-                    break;
-                }
-            }
-        }
-        StringBuilder builder = new StringBuilder();
-        for (int i = endIndex; i >= startIndex; i--) {
-            ChatHudLine.Visible chatHudLine = messages.get(i);
-            TextUtil.acceptOrderedText(builder, chatHudLine.content());
-        }
-        return builder.toString();
-    }
-}
diff --git a/src/main/java/blade/addon/mixin/HeldItemRendererMixin.java b/src/main/java/blade/addon/mixin/HeldItemRendererMixin.java
index b4d68d9..d6b3ec7 100644
--- a/src/main/java/blade/addon/mixin/HeldItemRendererMixin.java
+++ b/src/main/java/blade/addon/mixin/HeldItemRendererMixin.java
@@ -9,6 +9,7 @@ import net.minecraft.client.util.math.MatrixStack;
 import net.minecraft.item.ItemStack;
 import net.minecraft.item.Items;
 import net.minecraft.util.Arm;
+import net.minecraft.util.math.MathHelper;
 import org.spongepowered.asm.mixin.Mixin;
 import org.spongepowered.asm.mixin.Shadow;
 import org.spongepowered.asm.mixin.injection.At;
@@ -53,19 +54,21 @@ public abstract class HeldItemRendererMixin {
         this.equipProgressMainHand = 1;
     }
 
-    @Inject(method = "swingArm", at = @At("HEAD"), cancellable = true)
-    private void stopSwing(float swingProgress, float equipProgress, MatrixStack matrices, int armX, Arm arm, CallbackInfo ci) {
-
-        //TODO: refactor this
-        if (!SwingAnimation.shouldIgnore()) {
-            if (!DropAnimation.shouldProceed()) return;
-            ItemStack prevDropped = DropAnimation.getPrevDroppedItem();
-            if (prevDropped == null) return;
-        }
-
+    @Inject(method = "applyEquipOffset", at = @At("HEAD"), cancellable = true)
+    private void applyEquipOffset(MatrixStack matrices, Arm arm, float swingProgress, CallbackInfo ci) {
+        if (!SwingAnimation.shouldIgnore()) return;
+        int side = arm == Arm.RIGHT ? 1 : -1;
+        matrices.translate((float) side * 0.56F, -0.52F, -0.72F);
+        ci.cancel();
+    }
 
-        int i = arm == Arm.RIGHT ? 1 : -1;
-        matrices.translate((float) i * 0.56F, -0.52F, -0.72F);
+    @Inject(method = "swingArm", at = @At("HEAD"), cancellable = true)
+    private void swingArm(float swingProgress, MatrixStack matrixStack, int i, Arm arm, CallbackInfo ci) {
+        if (!SwingAnimation.shouldIgnore()) return;
+        float f = -0.4F * MathHelper.sin(MathHelper.sqrt(1) * (float) Math.PI);
+        float g = 0.2F * MathHelper.sin(MathHelper.sqrt(1) * (float) (Math.PI * 2));
+        float h = -0.2F * MathHelper.sin(1 * (float) Math.PI);
+        matrixStack.translate(i * f, g, h);
         ci.cancel();
     }
 }
diff --git a/src/main/java/blade/addon/utils/rendering/RenderUtils.java b/src/main/java/blade/addon/utils/rendering/RenderUtils.java
index 7967cf2..8d5056c 100644
--- a/src/main/java/blade/addon/utils/rendering/RenderUtils.java
+++ b/src/main/java/blade/addon/utils/rendering/RenderUtils.java
@@ -12,13 +12,13 @@ import net.minecraft.client.gui.DrawContext;
 import net.minecraft.client.network.ClientPlayerEntity;
 import net.minecraft.client.render.DrawStyle;
 import net.minecraft.client.render.VertexConsumer;
-import net.minecraft.client.render.VertexRendering;
 import net.minecraft.client.util.math.MatrixStack;
 import net.minecraft.text.Text;
 import net.minecraft.util.math.Box;
 import net.minecraft.util.math.ColorHelper;
 import net.minecraft.util.math.Vec3d;
 import net.minecraft.world.debug.gizmo.GizmoDrawing;
+import org.joml.Vector3f;
 
 public class RenderUtils {
 
@@ -130,10 +130,11 @@ public class RenderUtils {
         Vec3d playerPos = EntityUtil.getLerpedPos(player);
         double eyeHeight = player.getStandingEyeHeight();
         Vector3f lookat = new Vector3f(0, 0, -1f).rotate(context.worldState().cameraRenderState.orientation);
-
-        Vector3f startPos = playerPos.toVector3f().add(0f, (float)eyeHeight, 0f).add(lookat);
+        Vec3d startPos = playerPos.add(0, eyeHeight, 0).add(lookat.x, lookat.y + eyeHeight, lookat.z);
         Vec3d endPos = new Vec3d(x, y, z).subtract(playerPos).subtract(lookat.x, lookat.y + eyeHeight, lookat.z);
-        VertexRendering.drawVector(matrices, consumer, startPos, endPos, color);
+
+        //VertexRendering.drawVector(matrices, consumer, startPos, endPos, color);
+        GizmoDrawing.line(startPos, endPos, color);
     }
 
     public static void renderLineTo(WorldRenderContext context, MatrixStack matrices, VertexConsumer consumer, Vec3d pos, int color) {
@@ -145,12 +146,11 @@ public class RenderUtils {
             if (num >= 1e9) return String.format("%.1fB", num / 1e9);
             if (num >= 1e6) return String.format("%.1fM", num / 1e6);
             if (num >= 1e3) return String.format("%.1fK", num / 1e3);
-            return num + "";
         } else {
             if (num >= 1e9) return String.format("%.1fb", num / 1e9);
             if (num >= 1e6) return String.format("%.1fm", num / 1e6);
             if (num >= 1e3) return String.format("%.1fk", num / 1e3);
+        }
             return num + "";
         }
-    }
 }
diff --git a/src/main/java/blade/addon/utils/rendering/RenderingEvents.java b/src/main/java/blade/addon/utils/rendering/RenderingEvents.java
index 6d0f4d5..1e11835 100644
--- a/src/main/java/blade/addon/utils/rendering/RenderingEvents.java
+++ b/src/main/java/blade/addon/utils/rendering/RenderingEvents.java
@@ -116,6 +116,8 @@ public class RenderingEvents {
     }
 
     private static void debugLine(WorldRenderContext context) {
+        WorldRenderState worldState = context.worldState();
+        if (worldState == null) return;
         Vec3d camera = context.worldState().cameraRenderState.pos;
         MatrixStack matrices = context.matrices();
         if (matrices == null) return;
diff --git a/src/main/resources/blade-addons.mixins.json b/src/main/resources/blade-addons.mixins.json
index 0087730..a6b74a3 100644
--- a/src/main/resources/blade-addons.mixins.json
+++ b/src/main/resources/blade-addons.mixins.json
@@ -22,8 +22,6 @@
   },
   "client": [
     "BossBarHudMixin",
-    "ChatHudInvoker",
-    "ChatScreenMixin",
     "ClientPlayerEntityMixin",
     "ClientPlayNetworkHandlerMixin",
     "DrawContextMixin",
Merge pull request #4 from SteinGaming/new
refactor: incomplete 1.21.11 implementation
Blade · 2026-03-23 · 77fb512
查看变更
gradle.properties                                                            |  8 ++++----
 gradlew                                                                      |  0
 src/main/java/blade/addon/features/dungeon/ItemHighlight.java                |  5 ++++-
 src/main/java/blade/addon/features/dungeon/f7/RelicTimer.java                |  5 ++++-
 src/main/java/blade/addon/features/dungeon/f7/location/PositionMessages.java |  7 +++++--
 src/main/java/blade/addon/mixin/HandledScreenMixin.java                      |  4 ++--
 src/main/java/blade/addon/mixin/StatusEffectsDisplayMixin.java               | 11 ++++++++---
 src/main/java/blade/addon/utils/Waypoint.java                                |  6 +++++-
 src/main/java/blade/addon/utils/rendering/RenderLayers.java                  | 37 ++++++++++++++-----------------------
 src/main/java/blade/addon/utils/rendering/RenderUtils.java                   | 10 +++++++---
 src/main/java/blade/addon/utils/rendering/RenderingEvents.java               | 21 ++++++++++++++++-----
 11 files changed, 69 insertions(+), 45 deletions(-)
Merge branch 'new' into newBlade · 2026-03-23 · d2f62c4
查看变更
gradle.properties                                                                        |   2 +-
 src/main/java/blade/addon/Bladeaddons.java                                               |   4 ++++
 src/main/java/blade/addon/features/dungeon/ExplosiveShot.java                            |  10 +++++-----
 src/main/java/blade/addon/features/dungeon/RunStartValidator.java                        |   8 ++++----
 src/main/java/blade/addon/features/dungeon/WarpCooldown.java                             |   9 +++++----
 src/main/java/blade/addon/features/dungeon/f7/dragons/DragSpawnTimer.java                |   4 ++--
 src/main/java/blade/addon/features/dungeon/f7/dragons/Dragon.java                        |  17 ++++++++++-------
 src/main/java/blade/addon/features/dungeon/f7/dragons/DragonTracer.java                  |  24 ++++++++++++++++++++++++
 src/main/java/blade/addon/features/dungeon/f7/invincibility/InvincibilityTimer.java      |   6 +++---
 src/main/java/blade/addon/features/dungeon/f7/terms/LeapNotification.java                |  49 ++++++++++++++++++++++++++++++++-----------------
 src/main/java/blade/addon/features/filter/Filters.java                                   |   2 +-
 src/main/java/blade/addon/features/item/ItemRarity.java                                  |  22 +++++++++++-----------
 src/main/java/blade/addon/features/item/ItemRarityHighlight.java                         |  12 +++++++++++-
 src/main/java/blade/addon/features/other/ReaperDisplay.java                              |  40 ++++++++++++++++++++++++++++++++++++++++
 src/main/java/blade/addon/features/other/SearchBar.java                                  |  14 ++++++++++----
 src/main/java/blade/addon/features/other/SelectedPet.java                                |   8 ++++----
 src/main/java/blade/addon/features/other/SwingAnimation.java                             |  30 ++++++++++++++++++++++++++++++
 src/main/java/blade/addon/mixin/EnderPearlItemMixin.java                                 |  24 ++++++++++++++++++++++++
 src/main/java/blade/addon/mixin/HeldItemRendererMixin.java                               |  15 +++++++++++----
 src/main/java/blade/addon/mixin/LivingEntityMixin.java                                   |  23 +++++++++++++++++++++++
 src/main/java/blade/addon/mixin/ShovelItemMixin.java                                     |  23 +++++++++++++++++++++++
 src/main/java/blade/addon/utils/config/Config.java                                       |  17 ++++++++++++++---
 src/main/java/blade/addon/utils/config/components/Components.java                        |   4 ++++
 src/main/java/blade/addon/utils/config/values/ExtraOptions.java                          |   6 ++++++
 src/main/java/blade/addon/utils/config/values/Floor7.java                                |   9 +++++++++
 src/main/java/blade/addon/utils/config/values/Visual.java                                |   9 +++++++++
 src/main/java/blade/addon/utils/rendering/RenderUtils.java                               |  20 +++++++++++++++++++-
 src/main/java/blade/addon/utils/rendering/RenderingEvents.java                           |  19 +++++++++++++++++++
 src/main/resources/assets/blade-addons/textures/gui/sprites/pets/witch.png               | Bin 0 -> 3768 bytes
 src/main/resources/assets/blade-addons/textures/gui/sprites/rarity-background-circle.png | Bin 0 -> 570 bytes
 src/main/resources/assets/blade-addons/textures/gui/sprites/rarity-background.png        | Bin 0 -> 374 bytes
 src/main/resources/blade-addons.mixins.json                                              |   3 +++
 src/main/resources/fabric.mod.json                                                       |   4 ++--
 33 files changed, 363 insertions(+), 74 deletions(-)
- fixed leap notifications breaking weirdlyGabriel Ellnebrand · 2026-03-01 · 989b326
查看变更
src/main/java/blade/addon/features/dungeon/f7/terms/LeapNotification.java | 7 ++++---
 1 file changed, 4 insertions(+), 3 deletions(-)
diff --git a/src/main/java/blade/addon/features/dungeon/f7/terms/LeapNotification.java b/src/main/java/blade/addon/features/dungeon/f7/terms/LeapNotification.java
index e59e989..afb2457 100644
--- a/src/main/java/blade/addon/features/dungeon/f7/terms/LeapNotification.java
+++ b/src/main/java/blade/addon/features/dungeon/f7/terms/LeapNotification.java
@@ -58,11 +58,12 @@ public class LeapNotification {
             currentSpot = getSpot(player);
             if (currentSpot < 1) {
                 count = 0;
+                inBounds = false;
                 return;
+            } else {
+                inBounds = true;
             }
 
-            inBounds = currentSpot > 1;
-
             Box box = REGIONS[currentSpot - 1];
             count = getCount(world, player, box);
         });
@@ -97,7 +98,7 @@ public class LeapNotification {
     }
 
     public static boolean display() {
-        return Floor7.leapNotifications && inBounds;
+        return Floor7.leapNotifications && inBounds && Phase.inBoss() && Location.inDungeon();
     }
 
     public static void render(HUDComponent component, DrawContext context) {
- added a disable all chat filters toggle
- updated pregenerated stuff like contacts
Gabriel Ellnebrand · 2026-03-01 · bf94e76
查看变更
src/main/java/blade/addon/features/filter/Filters.java                     |   2 +-
 src/main/java/blade/addon/utils/config/Config.java                         |   5 +++--
 src/main/java/blade/addon/utils/config/values/ExtraOptions.java            |   3 +++
 src/main/resources/assets/blade-addons/textures/gui/sprites/pets/witch.png | Bin 0 -> 3768 bytes
 src/main/resources/fabric.mod.json                                         |   4 ++--
 5 files changed, 9 insertions(+), 5 deletions(-)
diff --git a/src/main/java/blade/addon/features/filter/Filters.java b/src/main/java/blade/addon/features/filter/Filters.java
index 571b7d0..aabc9a9 100644
--- a/src/main/java/blade/addon/features/filter/Filters.java
+++ b/src/main/java/blade/addon/features/filter/Filters.java
@@ -23,7 +23,7 @@ public class Filters {
     public static void init() {
         filterManager.load();
         ClientReceiveMessageEvents.ALLOW_GAME.register((message, overlay) -> {
-            if (overlay) return true;
+            if (overlay || ExtraOptions.disableAllFilters) return true;
             String string = message.getString();
 
             if (ExtraOptions.ignoreColorCodesFilter) {
diff --git a/src/main/java/blade/addon/utils/config/Config.java b/src/main/java/blade/addon/utils/config/Config.java
index 5b45c44..6631831 100644
--- a/src/main/java/blade/addon/utils/config/Config.java
+++ b/src/main/java/blade/addon/utils/config/Config.java
@@ -69,6 +69,7 @@ public class Config {
         ConfigSection chatFilter = new ConfigSection(Text.literal("Chat filter"));
         chatFilter.add(new ConfigButton(Text.literal("Edit"), () -> MinecraftClient.getInstance().setScreen(new FilterList())));
         chatFilter.add(new ConfigBool(Text.literal("Ignore color codes"), () -> ExtraOptions.ignoreColorCodesFilter, bool -> ExtraOptions.ignoreColorCodesFilter = bool));
+        chatFilter.add(new ConfigBool(Text.literal("Disable all filters"), () -> ExtraOptions.disableAllFilters, bool -> ExtraOptions.disableAllFilters = bool));
         general.add(chatFilter);
         return general;
     }
@@ -200,7 +201,7 @@ public class Config {
         goldor.add(new ConfigBool(Text.literal("Melody warning notification"), () -> Floor7.notifiyMelody, bool -> Floor7.notifiyMelody = bool));
         goldor.add(new ConfigBool(Text.literal("Send terminal time stamps"), () -> Floor7.terminalTimeStamps, bool -> Floor7.terminalTimeStamps = bool));
         goldor.add(new ConfigBool(Text.literal("Section completion notification"), () -> Floor7.sectionCompletionNotification, bool -> Floor7.sectionCompletionNotification = bool));
-        goldor.add(new ConfigSound(Text.literal("Section complete sound"), Floor7.sectionChangeSound, 2, 2, true));
+        goldor.add(new ConfigSound(Text.literal("Section complete sound"), Floor7.sectionChangeSound));
         goldor.add(new ConfigBool(Text.literal("Current section display"), () -> Floor7.showCurrentSection, bool -> Floor7.showCurrentSection = bool));
         floor7.add(goldor);
 
@@ -221,7 +222,7 @@ public class Config {
         locationNotifier.add(new ConfigBool(Text.literal("Display Location messages on screen"), () -> Floor7.displayLocationNotification, bool -> Floor7.displayLocationNotification = bool));
         locationNotifier.add(new ConfigBool(Text.literal("Hide your own notifications"), () -> Floor7.dontNotifiyForYourself, bool -> Floor7.dontNotifiyForYourself = bool));
         locationNotifier.add(new ConfigInt(Text.literal("Display duration (in client ticks)"), () -> Floor7.notificationDuration, num -> Floor7.notificationDuration = num, 1, 1, 20));
-        locationNotifier.add(new ConfigSound(Text.literal("Notification sound"), Floor7.atLocationSound, 2, 2, false));
+        locationNotifier.add(new ConfigSound(Text.literal("Notification sound"), Floor7.atLocationSound, 4, 2, false));
         locationNotifier.add(new ConfigInt(Text.literal("Sound repetitions"), () -> Floor7.notificationRepetitions, num -> Floor7.notificationRepetitions = num, 1, 0, 20));
         locationNotifier.add(new ConfigButton(Text.literal("Test notification"), () -> LocationNotifier.startNotification("Someone", " At <location>!!")));
 
diff --git a/src/main/java/blade/addon/utils/config/values/ExtraOptions.java b/src/main/java/blade/addon/utils/config/values/ExtraOptions.java
index ed45ee5..bcb70b9 100644
--- a/src/main/java/blade/addon/utils/config/values/ExtraOptions.java
+++ b/src/main/java/blade/addon/utils/config/values/ExtraOptions.java
@@ -138,4 +138,7 @@ public class ExtraOptions {
 
     @ConfigValue
     public static boolean enableReaperDisplay = false;
+
+    @ConfigValue
+    public static boolean disableAllFilters = false;
 }
diff --git a/src/main/resources/assets/blade-addons/textures/gui/sprites/pets/witch.png b/src/main/resources/assets/blade-addons/textures/gui/sprites/pets/witch.png
new file mode 100644
index 0000000..91a02c6
Binary files /dev/null and b/src/main/resources/assets/blade-addons/textures/gui/sprites/pets/witch.png differ
diff --git a/src/main/resources/fabric.mod.json b/src/main/resources/fabric.mod.json
index 007ba20..6100186 100644
--- a/src/main/resources/fabric.mod.json
+++ b/src/main/resources/fabric.mod.json
@@ -8,8 +8,8 @@
 		"BladeMasterGabe"
 	],
 	"contact": {
-		"homepage": "https://fabricmc.net/",
-		"sources": "https://github.com/FabricMC/fabric-example-mod"
+		"homepage": "https://modrinth.com/mod/blade-addons",
+		"sources": "https://github.com/BladeMasterGabe/blade-addons"
 	},
 	"license": "CC0-1.0",
 	"icon": "assets/blade-addons/icon.png",
- fixed player leap count assuming only 3 players for its color yet still saying "x/4" - added assume core and split ee2 for leap player countGabriel Ellnebrand · 2026-03-01 · 47ad109
查看变更
src/main/java/blade/addon/features/dungeon/f7/terms/LeapNotification.java | 52 +++++++++++++++++++++++++++++++++-------------------
 src/main/java/blade/addon/utils/config/Config.java                        |  7 ++++++-
 src/main/java/blade/addon/utils/config/values/Floor7.java                 |  6 ++++++
 3 files changed, 45 insertions(+), 20 deletions(-)
diff --git a/src/main/java/blade/addon/features/dungeon/f7/terms/LeapNotification.java b/src/main/java/blade/addon/features/dungeon/f7/terms/LeapNotification.java
index 1655255..e59e989 100644
--- a/src/main/java/blade/addon/features/dungeon/f7/terms/LeapNotification.java
+++ b/src/main/java/blade/addon/features/dungeon/f7/terms/LeapNotification.java
@@ -24,21 +24,24 @@ import java.util.List;
 public class LeapNotification {
 
     private static final Box[] REGIONS = {
-            new Box(90, 106, 50, 111, 145, 123),
+            new Box(91, 105, 46, 111, 127, 123),
             new Box(17, 106, 121, 108, 145, 143),
             new Box(-2, 106, 51, 20, 145, 142),
             new Box(-1, 26, 29, 191, 145, 58),
             new Box(3, 5, 0, 128, 48, 140)
     };
 
+    private static final Box RED_PILLAR_BOX = Box.of(new Vec3d(100, 116, 46), 2, 2, 2);
+    private static final Box SS_BOX = new Box(107, 119, 92, 108, 121, 95);
     private static final Box EE2_BOX = new Box(57, 108, 130, 59, 110, 132);
     private static final Box HEE2_BOX = new Box(59, 132, 138, 62, 133, 140);
     private static final Box EE3_BOX = new Box(1, 108, 103, 3, 110, 105);
     private static final Box CORE_BOX = new Box(53.5, 114, 49.5, 55.5, 116, 51.5);
     private static final Box RELIC_BOX = new Box(51.5, 3, 73.5, 57.5, 8, 79.5);
 
+
     private static int count = 0;
-    private static int currentSpot = 0;
+    private static int currentSpot = -1;
     private static boolean inBounds = false;
 
     public static void init() {
@@ -53,24 +56,28 @@ public class LeapNotification {
 
             if (player == null || world == null) return;
             currentSpot = getSpot(player);
-            if (currentSpot < 2) {
+            if (currentSpot < 1) {
                 count = 0;
-                inBounds = false;
                 return;
-            } else {
-                inBounds = true;
             }
+
+            inBounds = currentSpot > 1;
+
             Box box = REGIONS[currentSpot - 1];
-            List<Entity> entities = world.getOtherEntities(player, box);
-            int currentCount = 0;
-            for (Entity entity : entities) {
-                if (EntityUtil.isARealPlayer(entity)) {
-                    currentCount++;
-                }
+            count = getCount(world, player, box);
+        });
+    }
+
+    private static int getCount(ClientWorld world, ClientPlayerEntity player, Box box) {
+        List<Entity> entities = world.getOtherEntities(player, box);
+        int currentCount = 0;
+        for (Entity entity : entities) {
+            if (EntityUtil.isARealPlayer(entity)) {
+                currentCount++;
             }
+        }
 
-            count = currentCount;
-        });
+        return currentCount;
     }
 
     private static int getSpot(ClientPlayerEntity player) {
@@ -80,7 +87,13 @@ public class LeapNotification {
         else if (EE3_BOX.contains(pos)) return 3;
         else if (CORE_BOX.contains(pos)) return 4;
         else if (RELIC_BOX.contains(pos) && !Phase.inP5()) return 5;
-        else return 0;
+        else return -1;
+    }
+
+    private static int getMaxCount() {
+        if (currentSpot == 2 && Floor7.assumeSplitEE2) return 3;
+        if (currentSpot == 3 && Floor7.assumeCore) return 3;
+        return 4;
     }
 
     public static boolean display() {
@@ -88,10 +101,11 @@ public class LeapNotification {
     }
 
     public static void render(HUDComponent component, DrawContext context) {
-        String start;
-        if (currentSpot == 3) start = (count > 2 ? "§9" : "§4");
-        else start = (count > 3 ? "§9" : "§4");
+        int maxCount = getMaxCount();
+
+        String startFormat;
+        startFormat = (maxCount - count <= 1? "§9" : "§4");
 
-        RenderUtils.drawCenteredText(context, component, Text.literal(start + count + "§9/4 Players Leaped"));
+        RenderUtils.drawCenteredText(context, component, Text.literal(startFormat + count + "§9/" + maxCount + " Players Leaped"));
     }
 }
diff --git a/src/main/java/blade/addon/utils/config/Config.java b/src/main/java/blade/addon/utils/config/Config.java
index ea74544..5b45c44 100644
--- a/src/main/java/blade/addon/utils/config/Config.java
+++ b/src/main/java/blade/addon/utils/config/Config.java
@@ -151,9 +151,14 @@ public class Config {
     private static ConfigCategory floor7() {
         ConfigCategory floor7 = new ConfigCategory("Floor 7");
         floor7.add(new ConfigBool(Text.literal("Combine tick timers"), () -> Floor7.combineTickTimers, bool -> Floor7.combineTickTimers = bool));
-        floor7.add(new ConfigBool(Text.literal("Enable player leap count"), () -> Floor7.leapNotifications, bool -> Floor7.leapNotifications = bool));
         floor7.add(new ConfigBool(Text.literal("Capitalize health numbers"), () -> Floor7.capitalizeHealthNumbers, bool -> Floor7.capitalizeHealthNumbers = bool));
 
+        ConfigSection leapCount = new ConfigSection(Text.literal("Leap count"));
+        leapCount.add(new ConfigBool(Text.literal("Enable player leap count"), () -> Floor7.leapNotifications, bool -> Floor7.leapNotifications = bool));
+        leapCount.add(new ConfigBool(Text.literal("Assume core (max 3 on ee3)"), () -> Floor7.assumeCore, bool -> Floor7.assumeCore = bool));
+        leapCount.add(new ConfigBool(Text.literal("Assume split ee2 (max 3 on ee2)"), () -> Floor7.assumeSplitEE2, bool -> Floor7.assumeSplitEE2 = bool));
+        floor7.add(leapCount);
+
         ConfigSection waypoints = new ConfigSection(Text.literal("Waypoints"));
         waypoints.add(new ConfigBool(Text.literal("Enable boss waypoints"), () -> Floor7.enableBossWaypoints, bool -> Floor7.enableBossWaypoints = bool));
         waypoints.add(new ConfigBool(Text.literal("Enable placing waypoints"), BossWaypoints::getPlace, BossWaypoints::setPlace));
diff --git a/src/main/java/blade/addon/utils/config/values/Floor7.java b/src/main/java/blade/addon/utils/config/values/Floor7.java
index 90da7fd..4f98561 100644
--- a/src/main/java/blade/addon/utils/config/values/Floor7.java
+++ b/src/main/java/blade/addon/utils/config/values/Floor7.java
@@ -177,4 +177,10 @@ public class Floor7 {
 
     @ConfigValue
     public static boolean dragonTracer = false;
+
+    @ConfigValue
+    public static boolean assumeCore = true;
+
+    @ConfigValue
+    public static boolean assumeSplitEE2 = false;
 }
- added reaper duration display - fixed orange drag tracer being wrongGabriel Ellnebrand · 2026-02-22 · 4486968
查看变更
src/main/java/blade/addon/Bladeaddons.java                        |  2 ++
 src/main/java/blade/addon/features/dungeon/f7/dragons/Dragon.java | 10 +++++-----
 src/main/java/blade/addon/features/other/ReaperDisplay.java       | 40 ++++++++++++++++++++++++++++++++++++++++
 src/main/java/blade/addon/utils/config/Config.java                |  1 +
 src/main/java/blade/addon/utils/config/components/Components.java |  4 ++++
 src/main/java/blade/addon/utils/config/values/ExtraOptions.java   |  3 +++
 6 files changed, 55 insertions(+), 5 deletions(-)
diff --git a/src/main/java/blade/addon/Bladeaddons.java b/src/main/java/blade/addon/Bladeaddons.java
index 126eb8d..79fc730 100644
--- a/src/main/java/blade/addon/Bladeaddons.java
+++ b/src/main/java/blade/addon/Bladeaddons.java
@@ -56,6 +56,7 @@ import blade.addon.features.other.DianaNotifier;
 import blade.addon.features.other.KickedTimer;
 import blade.addon.features.other.PracticeSS;
 import blade.addon.features.other.RagDisplay;
+import blade.addon.features.other.ReaperDisplay;
 import blade.addon.features.other.SearchBar;
 import blade.addon.features.other.SelectedPet;
 import blade.addon.features.other.StunWaypoint;
@@ -162,5 +163,6 @@ public class Bladeaddons implements ModInitializer {
         BloodNotifier.init();
         CurrentSection.init();
         DragonTracer.init();
+        ReaperDisplay.init();
     }
 }
\ No newline at end of file
diff --git a/src/main/java/blade/addon/features/dungeon/f7/dragons/Dragon.java b/src/main/java/blade/addon/features/dungeon/f7/dragons/Dragon.java
index 3d581f4..b6c5419 100644
--- a/src/main/java/blade/addon/features/dungeon/f7/dragons/Dragon.java
+++ b/src/main/java/blade/addon/features/dungeon/f7/dragons/Dragon.java
@@ -4,11 +4,11 @@ import blade.addon.utils.dungeon.DungeonClass;
 import net.minecraft.util.math.Vec3d;
 
 public enum Dragon {
-    PURPLE(0xffff55ff, 0, 4, new Vec3d(56, 14, 125)),
-    BLUE(0xff55ffff, 1, 3, new Vec3d(84, 14, 94)),
-    RED(0xffff5555, 2, 2, new Vec3d (27, 14, 59)),
-    GREEN(0xff55ff55, 3, 1, new Vec3d(27, 14, 94)),
-    ORANGE(0xffffaa00, 4, 0, new Vec3d(84, 14, 94)),
+    PURPLE(0xffff55ff, 0, 4, new Vec3d(56.5, 14, 125.5)),
+    BLUE(0xff55ffff, 1, 3, new Vec3d(84.5, 14, 94.5)),
+    RED(0xffff5555, 2, 2, new Vec3d (27.5, 14, 59.5)),
+    GREEN(0xff55ff55, 3, 1, new Vec3d(27.5, 14, 94.5)),
+    ORANGE(0xffffaa00, 4, 0, new Vec3d(84.5, 14, 56.5)),
     NONE(0xffffffff, 99, 99, new Vec3d(0, 0, 0));
 
     final int color;
diff --git a/src/main/java/blade/addon/features/other/ReaperDisplay.java b/src/main/java/blade/addon/features/other/ReaperDisplay.java
new file mode 100644
index 0000000..33273a6
--- /dev/null
+++ b/src/main/java/blade/addon/features/other/ReaperDisplay.java
@@ -0,0 +1,40 @@
+package blade.addon.features.other;
+
+import blade.addon.utils.Constants;
+import blade.addon.utils.config.values.ExtraOptions;
+import blade.addon.utils.events.Events;
+import blade.addon.utils.rendering.RenderUtils;
+import config.practical.hud.HUDComponent;
+import net.minecraft.client.gui.DrawContext;
+import net.minecraft.sound.SoundEvents;
+
+public class ReaperDisplay {
+
+    private static final int TOTAL_TICKS = 20 * 6;
+
+    private static int tick = 0;
+
+    public static void init() {
+        Events.ON_SOUND.register((soundEvent, volume, pitch) -> {
+            if (!ExtraOptions.enableReaperDisplay) return false;
+            if (soundEvent != SoundEvents.ENTITY_ZOMBIE_VILLAGER_CURE) return false;
+            if (volume != 0.5 && pitch != 1.0) return false;
+
+            tick = TOTAL_TICKS;
+            return false;
+        });
+
+        Events.ON_SERVER_TICK.register(() -> {
+            tick = Math.max(tick - 1, 0);
+            return false;
+        });
+    }
+
+    public static boolean display() {
+        return tick > 0;
+    }
+
+    public static void render(HUDComponent component, DrawContext context) {
+        RenderUtils.drawTimer(component, context, tick, Constants.RED);
+    }
+}
diff --git a/src/main/java/blade/addon/utils/config/Config.java b/src/main/java/blade/addon/utils/config/Config.java
index c36be1c..ea74544 100644
--- a/src/main/java/blade/addon/utils/config/Config.java
+++ b/src/main/java/blade/addon/utils/config/Config.java
@@ -319,6 +319,7 @@ public class Config {
         extra.add(new ConfigBool(Text.literal("Title on arrow swap"), () -> ExtraOptions.arrowSwapNotification, bool -> ExtraOptions.arrowSwapNotification = bool));
         extra.add(new ConfigBool(Text.literal("Toggleable searchbar (ctrl + f)"), () -> ExtraOptions.toggleableSearchBar, bool -> ExtraOptions.toggleableSearchBar = bool));
         extra.add(new ConfigBool(Text.literal("Disable protect item (resets on launch)"), () -> ProtectItem.stopProtectItem, bool -> ProtectItem.stopProtectItem = bool));
+        extra.add(new ConfigBool(Text.literal("Display reaper duration"), () -> ExtraOptions.enableReaperDisplay, bool -> ExtraOptions.enableReaperDisplay = bool));
         //extra.add(new ConfigBool(Text.literal("Kuudra stun waypoint"), () -> ExtraOptions.stunWaypoint, bool -> ExtraOptions.stunWaypoint = bool));
 
 
diff --git a/src/main/java/blade/addon/utils/config/components/Components.java b/src/main/java/blade/addon/utils/config/components/Components.java
index d4cd64f..7dea28b 100644
--- a/src/main/java/blade/addon/utils/config/components/Components.java
+++ b/src/main/java/blade/addon/utils/config/components/Components.java
@@ -30,6 +30,7 @@ import blade.addon.features.item.HeldItemToolTip;
 import blade.addon.features.other.ArrowSwapper;
 import blade.addon.features.other.KickedTimer;
 import blade.addon.features.other.RagDisplay;
+import blade.addon.features.other.ReaperDisplay;
 import blade.addon.features.other.SelectedPet;
 import blade.addon.features.notifications.Notifications;
 import blade.addon.utils.config.values.Dungeons;
@@ -165,4 +166,7 @@ public class Components {
 
     @ConfigValue
     public static HUDComponent currentSectionDisplay = new HUDComponent(0, 0, 50, 10, 1, "Current section", CurrentSection::display,  CurrentSection::render, () -> Floor7.showCurrentSection);
+
+    @ConfigValue
+    public static HUDComponent reaperDisplay = new HUDComponent(0, 0, TICK_TIMER_WIDTH, 10, 1, "reaper display", ReaperDisplay::display, ReaperDisplay::render, () -> ExtraOptions.enableReaperDisplay);
 }
diff --git a/src/main/java/blade/addon/utils/config/values/ExtraOptions.java b/src/main/java/blade/addon/utils/config/values/ExtraOptions.java
index a66e4e3..ed45ee5 100644
--- a/src/main/java/blade/addon/utils/config/values/ExtraOptions.java
+++ b/src/main/java/blade/addon/utils/config/values/ExtraOptions.java
@@ -135,4 +135,7 @@ public class ExtraOptions {
 
     @ConfigValue
     public static boolean ignoreColorCodesNotification = false;
+
+    @ConfigValue
+    public static boolean enableReaperDisplay = false;
 }
- added circular rarity background - added dragon tracers (needs testing) - fixed some features not working if you filter out their detection msgs - bumped versionGabriel Ellnebrand · 2026-02-21 · 2a2690f
查看变更
gradle.properties                                                                        |   2 +-
 src/main/java/blade/addon/Bladeaddons.java                                               |   2 ++
 src/main/java/blade/addon/features/dungeon/ExplosiveShot.java                            |  10 +++++-----
 src/main/java/blade/addon/features/dungeon/RunStartValidator.java                        |   8 ++++----
 src/main/java/blade/addon/features/dungeon/WarpCooldown.java                             |   9 +++++----
 src/main/java/blade/addon/features/dungeon/f7/dragons/DragSpawnTimer.java                |   4 ++--
 src/main/java/blade/addon/features/dungeon/f7/dragons/Dragon.java                        |  17 ++++++++++-------
 src/main/java/blade/addon/features/dungeon/f7/dragons/DragonTracer.java                  |  24 ++++++++++++++++++++++++
 src/main/java/blade/addon/features/dungeon/f7/invincibility/InvincibilityTimer.java      |   6 +++---
 src/main/java/blade/addon/features/item/ItemRarity.java                                  |  22 +++++++++++-----------
 src/main/java/blade/addon/features/item/ItemRarityHighlight.java                         |  12 +++++++++++-
 src/main/java/blade/addon/features/other/SelectedPet.java                                |   8 ++++----
 src/main/java/blade/addon/utils/config/Config.java                                       |   2 ++
 src/main/java/blade/addon/utils/config/values/Floor7.java                                |   3 +++
 src/main/java/blade/addon/utils/config/values/Visual.java                                |   3 +++
 src/main/java/blade/addon/utils/rendering/RenderUtils.java                               |  21 ++++++++++++++++++++-
 src/main/java/blade/addon/utils/rendering/RenderingEvents.java                           |  19 +++++++++++++++++++
 src/main/resources/assets/blade-addons/textures/gui/sprites/rarity-background-circle.png | Bin 0 -> 570 bytes
 src/main/resources/assets/blade-addons/textures/gui/sprites/rarity-background.png        | Bin 0 -> 374 bytes
 19 files changed, 129 insertions(+), 43 deletions(-)
diff --git a/gradle.properties b/gradle.properties
index 11c2c78..b9aaccc 100644
--- a/gradle.properties
+++ b/gradle.properties
@@ -13,7 +13,7 @@ loader_version=0.18.4
 loom_version=1.15-SNAPSHOT
 
 # Mod Properties
-mod_version=2.3.5
+mod_version=2.3.6
 maven_group=blade.addon
 archives_base_name=blade-addons
 
diff --git a/src/main/java/blade/addon/Bladeaddons.java b/src/main/java/blade/addon/Bladeaddons.java
index 21eca8c..126eb8d 100644
--- a/src/main/java/blade/addon/Bladeaddons.java
+++ b/src/main/java/blade/addon/Bladeaddons.java
@@ -17,6 +17,7 @@ import blade.addon.features.dungeon.f7.PredevTimer;
 import blade.addon.features.dungeon.f7.RelicTimer;
 import blade.addon.features.dungeon.f7.dragons.DragSpawnTimer;
 import blade.addon.features.dungeon.f7.dragons.DragonHealth;
+import blade.addon.features.dungeon.f7.dragons.DragonTracer;
 import blade.addon.features.dungeon.f7.invincibility.InvincibilityDuration;
 import blade.addon.features.dungeon.f7.invincibility.InvincibilityTimer;
 import blade.addon.features.dungeon.f7.invincibility.MaskHighlight;
@@ -160,5 +161,6 @@ public class Bladeaddons implements ModInitializer {
         MaxorTickTimer.init();
         BloodNotifier.init();
         CurrentSection.init();
+        DragonTracer.init();
     }
 }
\ No newline at end of file
diff --git a/src/main/java/blade/addon/features/dungeon/ExplosiveShot.java b/src/main/java/blade/addon/features/dungeon/ExplosiveShot.java
index dd8fb0e..37f47de 100644
--- a/src/main/java/blade/addon/features/dungeon/ExplosiveShot.java
+++ b/src/main/java/blade/addon/features/dungeon/ExplosiveShot.java
@@ -4,7 +4,7 @@ import blade.addon.utils.Location;
 import blade.addon.utils.Misc;
 import blade.addon.utils.config.values.Dungeons;
 import blade.addon.utils.dungeon.Phase;
-import net.fabricmc.fabric.api.client.message.v1.ClientReceiveMessageEvents;
+import blade.addon.utils.events.Events;
 import net.minecraft.text.Text;
 
 import java.text.DecimalFormat;
@@ -19,9 +19,9 @@ public class ExplosiveShot {
 
 
     public static void init() {
-        ClientReceiveMessageEvents.GAME.register((message, overlay) -> {
-            if (!Location.inDungeon() || !Dungeons.calculateCriticalHit) return;
-            if (Dungeons.onlyInBoss && !Phase.inBoss()) return;
+        Events.ON_GAME_MESSAGE.register(message -> {
+            if (!Location.inDungeon() || !Dungeons.calculateCriticalHit) return false;
+            if (Dungeons.onlyInBoss && !Phase.inBoss()) return false;
 
             String string = message.getString();
 
@@ -42,7 +42,7 @@ public class ExplosiveShot {
                 } catch (NumberFormatException ignored) {
                 }
             }
-
+            return false;
         });
     }
 
diff --git a/src/main/java/blade/addon/features/dungeon/RunStartValidator.java b/src/main/java/blade/addon/features/dungeon/RunStartValidator.java
index 102fc7c..0cda68a 100644
--- a/src/main/java/blade/addon/features/dungeon/RunStartValidator.java
+++ b/src/main/java/blade/addon/features/dungeon/RunStartValidator.java
@@ -8,7 +8,6 @@ import blade.addon.utils.dungeon.Phase;
 import blade.addon.utils.events.Events;
 import blade.addon.utils.rendering.RenderUtils;
 import config.practical.hud.HUDComponent;
-import net.fabricmc.fabric.api.client.message.v1.ClientReceiveMessageEvents;
 import net.minecraft.client.MinecraftClient;
 import net.minecraft.client.gui.DrawContext;
 import net.minecraft.client.network.ClientPlayerEntity;
@@ -37,16 +36,17 @@ public class RunStartValidator {
     private static boolean hasTicked = false;
 
     public static void init() {
-        ClientReceiveMessageEvents.GAME.register((message, overlay) -> {
-            if (!Location.inDungeon() || Phase.runStarted() || !Dungeons.detectDuplicateClass) return;
+        Events.ON_GAME_MESSAGE.register(message -> {
+            if (!Location.inDungeon() || Phase.runStarted() || !Dungeons.detectDuplicateClass) return false;
 
             Matcher matcher = STARTING_PATTERN.matcher(message.getString());
-            if (!matcher.find()) return;
+            if (!matcher.find()) return false;
             hasTicked = true;
 
             if (validate()) {
                 Scheduler.scheduleSound(SoundEvents.BLOCK_NOTE_BLOCK_PLING.value(), 2, 1);
             }
+            return false;
         });
 
         Events.ON_LOCATION_CHANGE.register(location -> {
diff --git a/src/main/java/blade/addon/features/dungeon/WarpCooldown.java b/src/main/java/blade/addon/features/dungeon/WarpCooldown.java
index 11dd40c..a7ea754 100644
--- a/src/main/java/blade/addon/features/dungeon/WarpCooldown.java
+++ b/src/main/java/blade/addon/features/dungeon/WarpCooldown.java
@@ -1,9 +1,9 @@
 package blade.addon.features.dungeon;
 
 import blade.addon.utils.config.values.Dungeons;
+import blade.addon.utils.events.Events;
 import blade.addon.utils.rendering.RenderUtils;
 import config.practical.hud.HUDComponent;
-import net.fabricmc.fabric.api.client.message.v1.ClientReceiveMessageEvents;
 import net.minecraft.client.gui.DrawContext;
 
 import java.util.regex.Matcher;
@@ -18,16 +18,17 @@ public class WarpCooldown {
     private static boolean displayCooldown = false;
 
     public static void init() {
-        ClientReceiveMessageEvents.GAME.register((message, overlay) -> {
-            if (!Dungeons.enableWarpCooldown) return;
+        Events.ON_GAME_MESSAGE.register(message -> {
+            if (!Dungeons.enableWarpCooldown) return false;
 
-            if ((endTime - System.currentTimeMillis()) > 0) return;
+            if ((endTime - System.currentTimeMillis()) > 0) return false;
 
             Matcher matcher = PATTERN.matcher(message.getString());
             if (matcher.find()) {
                 endTime = System.currentTimeMillis() + WARP_COOLDOWN_MS;
                 displayCooldown = true;
             }
+            return false;
         });
     }
 
diff --git a/src/main/java/blade/addon/features/dungeon/f7/dragons/DragSpawnTimer.java b/src/main/java/blade/addon/features/dungeon/f7/dragons/DragSpawnTimer.java
index 69bddb9..683e2f1 100644
--- a/src/main/java/blade/addon/features/dungeon/f7/dragons/DragSpawnTimer.java
+++ b/src/main/java/blade/addon/features/dungeon/f7/dragons/DragSpawnTimer.java
@@ -31,8 +31,8 @@ public class DragSpawnTimer {
 
     private static final int SPAWN_DURATION = 100;
 
-    private static Dragon currentDragon = Dragon.NONE;
-    private static boolean hasDoneSplit = false;
+    static Dragon currentDragon = Dragon.NONE;
+    static boolean hasDoneSplit = false;
     private static int tick = 0;
 
     public static void init() {
diff --git a/src/main/java/blade/addon/features/dungeon/f7/dragons/Dragon.java b/src/main/java/blade/addon/features/dungeon/f7/dragons/Dragon.java
index dcf7346..3d581f4 100644
--- a/src/main/java/blade/addon/features/dungeon/f7/dragons/Dragon.java
+++ b/src/main/java/blade/addon/features/dungeon/f7/dragons/Dragon.java
@@ -1,23 +1,26 @@
 package blade.addon.features.dungeon.f7.dragons;
 
 import blade.addon.utils.dungeon.DungeonClass;
+import net.minecraft.util.math.Vec3d;
 
 public enum Dragon {
-    PURPLE(0xffff55ff, 0, 4),
-    BLUE(0xff55ffff, 1, 3),
-    RED(0xffff5555, 2, 2),
-    GREEN(0xff55ff55, 3, 1),
-    ORANGE(0xffffaa00, 4, 0),
-    NONE(0xffffffff, 99, 99);
+    PURPLE(0xffff55ff, 0, 4, new Vec3d(56, 14, 125)),
+    BLUE(0xff55ffff, 1, 3, new Vec3d(84, 14, 94)),
+    RED(0xffff5555, 2, 2, new Vec3d (27, 14, 59)),
+    GREEN(0xff55ff55, 3, 1, new Vec3d(27, 14, 94)),
+    ORANGE(0xffffaa00, 4, 0, new Vec3d(84, 14, 94)),
+    NONE(0xffffffff, 99, 99, new Vec3d(0, 0, 0));
 
     final int color;
     final int archPrio;
     final int bersPrio;
+    final Vec3d spawnPos;
 
-    Dragon(int color, int archPrio, int bersPrio) {
+    Dragon(int color, int archPrio, int bersPrio, Vec3d spawnPos) {
         this.color = color;
         this.archPrio = archPrio;
         this.bersPrio = bersPrio;
+        this.spawnPos = spawnPos;
     }
 
     //checks are from valley addons
diff --git a/src/main/java/blade/addon/features/dungeon/f7/dragons/DragonTracer.java b/src/main/java/blade/addon/features/dungeon/f7/dragons/DragonTracer.java
new file mode 100644
index 0000000..d63028b
--- /dev/null
+++ b/src/main/java/blade/addon/features/dungeon/f7/dragons/DragonTracer.java
@@ -0,0 +1,24 @@
+package blade.addon.features.dungeon.f7.dragons;
+
+import blade.addon.utils.config.values.Floor7;
+import blade.addon.utils.rendering.RenderUtils;
+import blade.addon.utils.rendering.RenderingEvents;
+import net.fabricmc.fabric.api.client.rendering.v1.world.WorldRenderContext;
+import net.minecraft.client.render.VertexConsumer;
+import net.minecraft.client.util.math.MatrixStack;
+
+public class DragonTracer {
+
+    public static void init() {
+        RenderingEvents.LINE.register(DragonTracer::render);
+    }
+
+    private static void render(WorldRenderContext context, MatrixStack matrixStack, VertexConsumer consumer) {
+        if (!Floor7.dragonTracer) return;
+
+        Dragon dragon = DragSpawnTimer.currentDragon;
+        if (dragon == null || dragon == Dragon.NONE || !DragSpawnTimer.hasDoneSplit) return;
+
+        RenderUtils.renderLineTo(context, matrixStack, consumer, dragon.spawnPos, dragon.color);
+    }
+}
diff --git a/src/main/java/blade/addon/features/dungeon/f7/invincibility/InvincibilityTimer.java b/src/main/java/blade/addon/features/dungeon/f7/invincibility/InvincibilityTimer.java
index 3ab13d9..3acb628 100644
--- a/src/main/java/blade/addon/features/dungeon/f7/invincibility/InvincibilityTimer.java
+++ b/src/main/java/blade/addon/features/dungeon/f7/invincibility/InvincibilityTimer.java
@@ -8,7 +8,6 @@ import blade.addon.utils.config.values.Dungeons;
 import blade.addon.utils.dungeon.Phase;
 import blade.addon.utils.events.Events;
 import config.practical.hud.HUDComponent;
-import net.fabricmc.fabric.api.client.message.v1.ClientReceiveMessageEvents;
 import net.minecraft.client.MinecraftClient;
 import net.minecraft.client.gl.RenderPipelines;
 import net.minecraft.client.gui.DrawContext;
@@ -79,10 +78,11 @@ public class InvincibilityTimer {
         Events.ON_PET.register(name -> phoenixOn = name.equals("Phoenix"));
 
 
-        ClientReceiveMessageEvents.GAME.register((message, overlay) -> {
+        Events.ON_GAME_MESSAGE.register(text -> {
             if (Location.inDungeon()) {
-                parseMessage(message);
+                parseMessage(text);
             }
+            return false;
         });
     }
 
diff --git a/src/main/java/blade/addon/features/item/ItemRarity.java b/src/main/java/blade/addon/features/item/ItemRarity.java
index 4012d54..7f3652e 100644
--- a/src/main/java/blade/addon/features/item/ItemRarity.java
+++ b/src/main/java/blade/addon/features/item/ItemRarity.java
@@ -2,17 +2,17 @@ package blade.addon.features.item;
 
 public enum ItemRarity {
     NONE(0x0),
-    COMMON(0x99dddddd),
-    UNCOMMON(0x8842ab42),
-    RARE(0x884c4cd0),
-    EPIC(0x88671067),
-    LEGENDARY(0x88cf8d0a),
-    MYTHIC(0x88ff55ff),
-    DIVINE(0x884fe4e4),
-    SPECIAL(0x88ff5555),
-    VERY_SPECIAL(0x88c44747),
-    ULTIMATE(0x88a10202),
-    ADMIN(0x88aa0000);
+    COMMON(0xffdddddd),
+    UNCOMMON(0xff42ab42),
+    RARE(0xff4c4cd0),
+    EPIC(0xff671067),
+    LEGENDARY(0xffcf8d0a),
+    MYTHIC(0xffff55ff),
+    DIVINE(0xff4fe4e4),
+    SPECIAL(0xffff5555),
+    VERY_SPECIAL(0xffc44747),
+    ULTIMATE(0xffa10202),
+    ADMIN(0xffaa0000);
 
     private final int color;
 
diff --git a/src/main/java/blade/addon/features/item/ItemRarityHighlight.java b/src/main/java/blade/addon/features/item/ItemRarityHighlight.java
index 1d1bfe5..a0d63e1 100644
--- a/src/main/java/blade/addon/features/item/ItemRarityHighlight.java
+++ b/src/main/java/blade/addon/features/item/ItemRarityHighlight.java
@@ -1,17 +1,23 @@
 package blade.addon.features.item;
 
+import blade.addon.utils.Constants;
 import blade.addon.utils.config.values.Visual;
 import blade.addon.utils.rendering.DrawEvents;
+import net.minecraft.client.gl.RenderPipelines;
 import net.minecraft.client.gui.DrawContext;
 import net.minecraft.component.DataComponentTypes;
 import net.minecraft.component.type.LoreComponent;
 import net.minecraft.item.ItemStack;
 import net.minecraft.text.Text;
+import net.minecraft.util.Identifier;
 
 import java.util.List;
 
 public class ItemRarityHighlight {
 
+    private static final Identifier NORMAL_BACKGROUND = Identifier.of(Constants.NAMESPACE, "rarity-background");
+    private static final Identifier CIRCLE_BACKGROUND = Identifier.of(Constants.NAMESPACE, "rarity-background-circle");
+
     public static void init() {
         DrawEvents.INVENTORY_SLOT_BEFORE.register(ItemRarityHighlight::draw);
         DrawEvents.HUD_SLOT_BEFORE.register(ItemRarityHighlight::draw);
@@ -30,7 +36,11 @@ public class ItemRarityHighlight {
         if (!holder.blade_addons$hasItemRarity()) return;
 
         ItemRarity rarity = holder.blade_addons$getItemRarity();
-        context.fill(x, y, x + 16, y + 16, rarity.getColor());
+        if (Visual.circularRarityBackground) {
+            context.drawGuiTexture(RenderPipelines.GUI_TEXTURED, CIRCLE_BACKGROUND, x, y, 16, 16, rarity.getColor());
+        } else {
+            context.drawGuiTexture(RenderPipelines.GUI_TEXTURED, NORMAL_BACKGROUND, x, y, 16, 16, rarity.getColor());
+        }
     }
 
     public static ItemRarity getRarity(ItemStack item) {
diff --git a/src/main/java/blade/addon/features/other/SelectedPet.java b/src/main/java/blade/addon/features/other/SelectedPet.java
index 7ec28a9..721f3dd 100644
--- a/src/main/java/blade/addon/features/other/SelectedPet.java
+++ b/src/main/java/blade/addon/features/other/SelectedPet.java
@@ -8,7 +8,6 @@ import blade.addon.utils.events.Events;
 import blade.addon.utils.rendering.RenderUtils;
 import config.practical.hud.HUDComponent;
 import net.fabricmc.fabric.api.client.event.lifecycle.v1.ClientTickEvents;
-import net.fabricmc.fabric.api.client.message.v1.ClientReceiveMessageEvents;
 import net.minecraft.client.MinecraftClient;
 import net.minecraft.client.font.TextRenderer;
 import net.minecraft.client.gl.RenderPipelines;
@@ -43,20 +42,20 @@ public class SelectedPet {
     private static int tick = 0;
 
     public static void init() {
-        ClientReceiveMessageEvents.GAME.register((message, overlay) -> {
+        Events.ON_GAME_MESSAGE.register(message -> {
             String string = message.getString();
             Matcher matcher = MANUAL_EQUIP_PET_PATTERN.matcher(string);
             if (matcher.find()) {
 
                 if (matcher.group(1).equals("despawned")) {
                     despawnPet();
-                    return;
+                    return false;
                 }
 
                 String name = matcher.group(2).replace(" ✦", "");
                 Style style = getStyle(message, name);
                 summonPet(name, Text.literal(name).setStyle(style), getLevel(string), false);
-                return;
+                return false;
             }
 
             String unformattedLine = string.replaceAll("§.", "");
@@ -72,6 +71,7 @@ public class SelectedPet {
 
                 summonPet(name, Text.literal(textName), getLevel(unformattedLine), true);
             }
+            return false;
         });
 
         Events.ON_PLAYER_ENTRY.register(receivedEntry -> {
diff --git a/src/main/java/blade/addon/utils/config/Config.java b/src/main/java/blade/addon/utils/config/Config.java
index dbbf5fc..c36be1c 100644
--- a/src/main/java/blade/addon/utils/config/Config.java
+++ b/src/main/java/blade/addon/utils/config/Config.java
@@ -238,6 +238,7 @@ public class Config {
         dragon.add(new ConfigBool(Text.literal("Send sound on dragon spawn"), () -> Floor7.sendSoundOnDragSpawn, bool -> Floor7.sendSoundOnDragSpawn = bool));
         dragon.add(new ConfigOptions<>(Text.literal("Healer prio"), DragSpawnTimer.Team.values(), () -> Floor7.healerTeam, team -> Floor7.healerTeam = team));
         dragon.add(new ConfigBool(Text.literal("Render dragon health"), () -> Floor7.dragonHealth, bool -> Floor7.dragonHealth = bool));
+        dragon.add(new ConfigBool(Text.literal("Render dragon spawn tracer"), () -> Floor7.dragonTracer, bool -> Floor7.dragonTracer = bool));
         floor7.add(dragon);
 
         return floor7;
@@ -386,6 +387,7 @@ public class Config {
         visual.add(new ConfigBool(Text.literal("Hide arrows stuck to entities"), () -> Visual.hideStuckArrows, bool -> Visual.hideStuckArrows = bool));
         visual.add(new ConfigBool(Text.literal("Hide dead entities"), () -> Visual.hideDeadEntities, bool -> Visual.hideDeadEntities = bool));
         visual.add(new ConfigBool(Text.literal("Item rarity background"), () -> Visual.itemRarityBackground, bool -> Visual.itemRarityBackground = bool));
+        visual.add(new ConfigBool(Text.literal("Circular rarity background"), () -> Visual.circularRarityBackground, bool -> Visual.circularRarityBackground = bool));
         visual.add(new ConfigBool(Text.literal("Hide potion effects overlay"), () -> Visual.hideStatusOverLay, bool -> Visual.hideStatusOverLay = bool));
         visual.add(new ConfigBool(Text.literal("Disable glowing"), () -> Visual.disableGlowing, bool -> Visual.disableGlowing = bool));
         visual.add(new ConfigBool(Text.literal("Draw item starCount"), () -> Visual.drawStarCount, bool -> Visual.drawStarCount = bool));
diff --git a/src/main/java/blade/addon/utils/config/values/Floor7.java b/src/main/java/blade/addon/utils/config/values/Floor7.java
index 7cc8a28..90da7fd 100644
--- a/src/main/java/blade/addon/utils/config/values/Floor7.java
+++ b/src/main/java/blade/addon/utils/config/values/Floor7.java
@@ -174,4 +174,7 @@ public class Floor7 {
 
     @ConfigValue
     public static boolean sectionPrevObjective = false;
+
+    @ConfigValue
+    public static boolean dragonTracer = false;
 }
diff --git a/src/main/java/blade/addon/utils/config/values/Visual.java b/src/main/java/blade/addon/utils/config/values/Visual.java
index 917a208..47238bd 100644
--- a/src/main/java/blade/addon/utils/config/values/Visual.java
+++ b/src/main/java/blade/addon/utils/config/values/Visual.java
@@ -52,4 +52,7 @@ public class Visual {
     @ConfigValue
     public static boolean stopPearlSwing = false;
 
+    @ConfigValue
+    public static boolean circularRarityBackground = false;
+
 }
diff --git a/src/main/java/blade/addon/utils/rendering/RenderUtils.java b/src/main/java/blade/addon/utils/rendering/RenderUtils.java
index 3c850b7..d6e7c56 100644
--- a/src/main/java/blade/addon/utils/rendering/RenderUtils.java
+++ b/src/main/java/blade/addon/utils/rendering/RenderUtils.java
@@ -3,6 +3,7 @@ package blade.addon.utils.rendering;
 import blade.addon.utils.Constants;
 import blade.addon.utils.config.values.ExtraOptions;
 import blade.addon.utils.config.values.Floor7;
+import blade.addon.utils.data.EntityUtil;
 import config.practical.hud.HUDComponent;
 import net.fabricmc.fabric.api.client.rendering.v1.world.WorldRenderContext;
 import net.minecraft.client.MinecraftClient;
@@ -15,6 +16,7 @@ import net.minecraft.client.util.math.MatrixStack;
 import net.minecraft.text.Text;
 import net.minecraft.util.math.Box;
 import net.minecraft.util.math.Vec3d;
+import org.joml.Vector3f;
 
 public class RenderUtils {
 
@@ -117,7 +119,24 @@ public class RenderUtils {
         renderText(context, matrices, text, pos.x, pos.y, pos.z, scale);
     }
 
-    public static String formatNumber(float num) {
+    public static void renderLineTo(WorldRenderContext context, MatrixStack matrices, VertexConsumer consumer, double x, double y, double z, int color) {
+        ClientPlayerEntity player = MinecraftClient.getInstance().player;
+        if (player == null) return;
+
+        Vec3d playerPos = EntityUtil.getLerpedPos(player);
+        double eyeHeight = player.getStandingEyeHeight();
+        Vector3f lookat = new Vector3f(0, 0, -1f).rotate(context.worldState().cameraRenderState.orientation);
+
+        Vector3f startPos = playerPos.toVector3f().add(0f, (float)eyeHeight, 0f).add(lookat);
+        Vec3d endPos = new Vec3d(x, y, z).subtract(playerPos).subtract(lookat.x, lookat.y + eyeHeight, lookat.z);
+        VertexRendering.drawVector(matrices, consumer, startPos, endPos, color);
+    }
+
+    public static void renderLineTo(WorldRenderContext context, MatrixStack matrices, VertexConsumer consumer, Vec3d pos, int color) {
+        renderLineTo(context, matrices, consumer, pos.x, pos.y, pos.z, color);
+    }
+
+        public static String formatNumber(float num) {
         if (Floor7.capitalizeHealthNumbers) {
             if (num >= 1e9) return String.format("%.1fB", num / 1e9);
             if (num >= 1e6) return String.format("%.1fM", num / 1e6);
diff --git a/src/main/java/blade/addon/utils/rendering/RenderingEvents.java b/src/main/java/blade/addon/utils/rendering/RenderingEvents.java
index b5147d6..46fad78 100644
--- a/src/main/java/blade/addon/utils/rendering/RenderingEvents.java
+++ b/src/main/java/blade/addon/utils/rendering/RenderingEvents.java
@@ -15,6 +15,8 @@ public class RenderingEvents {
     public static RenderHandler FILLED_ENTITY = new RenderHandler();
     public static RenderHandler OUTLINE_ENTITY = new RenderHandler();
     public static RenderHandler NO_DEPTH_OUTLINE_ENTITY = new RenderHandler();
+    public static RenderHandler LINE = new RenderHandler();
+
 
     public static void init() {
         WorldRenderEvents.BEFORE_DEBUG_RENDER.register(RenderingEvents::filled);
@@ -22,6 +24,8 @@ public class RenderingEvents {
         WorldRenderEvents.AFTER_ENTITIES.register(RenderingEvents::entityFilled);
         WorldRenderEvents.AFTER_ENTITIES.register(RenderingEvents::entityOutline);
         WorldRenderEvents.AFTER_ENTITIES.register(RenderingEvents::entityOutlineNoDepth);
+        WorldRenderEvents.BEFORE_DEBUG_RENDER.register(RenderingEvents::debugLine);
+
     }
 
     private static void filled(WorldRenderContext context) {
@@ -100,4 +104,19 @@ public class RenderingEvents {
         matrices.pop();
     }
 
+    private static void debugLine(WorldRenderContext context) {
+        Vec3d camera = context.worldState().cameraRenderState.pos;
+        MatrixStack matrices = context.matrices();
+        if (matrices == null) return;
+        matrices.push();
+        matrices.translate(-camera.x, -camera.y, -camera.z);
+
+        VertexConsumerProvider consumers = context.consumers();
+        if (consumers == null) return;
+        VertexConsumer consumer = consumers.getBuffer(RenderLayers.getOutline(4, true));
+
+        LINE.invoke(renderingEvent -> renderingEvent.render(context, matrices, consumer));
+        matrices.pop();
+    }
+
 }
diff --git a/src/main/resources/assets/blade-addons/textures/gui/sprites/rarity-background-circle.png b/src/main/resources/assets/blade-addons/textures/gui/sprites/rarity-background-circle.png
new file mode 100644
index 0000000..0f074ce
Binary files /dev/null and b/src/main/resources/assets/blade-addons/textures/gui/sprites/rarity-background-circle.png differ
diff --git a/src/main/resources/assets/blade-addons/textures/gui/sprites/rarity-background.png b/src/main/resources/assets/blade-addons/textures/gui/sprites/rarity-background.png
new file mode 100644
index 0000000..9c7ce5b
Binary files /dev/null and b/src/main/resources/assets/blade-addons/textures/gui/sprites/rarity-background.png differ
- added press enter to set it as the inputed string - added stop swinging when shovel flattening (clicking on dirt with like aotv) - added stop swinging when throwing a pearlGabriel Ellnebrand · 2026-02-20 · 6db4884
查看变更
src/main/java/blade/addon/features/other/SearchBar.java      | 14 ++++++++++----
 src/main/java/blade/addon/features/other/SwingAnimation.java | 30 ++++++++++++++++++++++++++++++
 src/main/java/blade/addon/mixin/EnderPearlItemMixin.java     | 24 ++++++++++++++++++++++++
 src/main/java/blade/addon/mixin/HeldItemRendererMixin.java   | 13 ++++++++++---
 src/main/java/blade/addon/mixin/LivingEntityMixin.java       | 23 +++++++++++++++++++++++
 src/main/java/blade/addon/mixin/ShovelItemMixin.java         | 23 +++++++++++++++++++++++
 src/main/java/blade/addon/utils/config/Config.java           |  2 ++
 src/main/java/blade/addon/utils/config/values/Visual.java    |  6 ++++++
 src/main/resources/blade-addons.mixins.json                  |  3 +++
 9 files changed, 131 insertions(+), 7 deletions(-)
diff --git a/src/main/java/blade/addon/features/other/SearchBar.java b/src/main/java/blade/addon/features/other/SearchBar.java
index d6f6c01..694079a 100644
--- a/src/main/java/blade/addon/features/other/SearchBar.java
+++ b/src/main/java/blade/addon/features/other/SearchBar.java
@@ -50,7 +50,7 @@ public class SearchBar {
         searchBar.render(context, mouseX, mouseY, deltaTicks);
 
         if (!Double.isNaN(parsedValue)) {
-            String expression = "  §e= §2"+ RenderUtils.formatNumber((float) parsedValue);
+            String expression = "  §e= §2" + RenderUtils.formatNumber((float) parsedValue);
             TextRenderer textRenderer = MinecraftClient.getInstance().textRenderer;
             if (textRenderer == null) return;
 
@@ -68,9 +68,15 @@ public class SearchBar {
         if (ctrlIsPressed && input.key() == GLFW.GLFW_KEY_F) {
             shouldDisplay = !shouldDisplay;
             return true;
-        } else if (shouldDisplay() && searchBar.isFocused() && input.key() != GLFW.GLFW_KEY_ESCAPE) {
-            searchBar.keyPressed(input);
-            return true;
+        } else if (shouldDisplay() && searchBar.isFocused()) {
+            if (input.key() == GLFW.GLFW_KEY_ENTER) {
+                if (!Double.isNaN(parsedValue)) {
+                    searchBar.setText(RenderUtils.formatNumber((float) parsedValue));
+                }
+            } else if (input.key() != GLFW.GLFW_KEY_ESCAPE) {
+                searchBar.keyPressed(input);
+                return true;
+            }
         }
         return false;
     }
diff --git a/src/main/java/blade/addon/features/other/SwingAnimation.java b/src/main/java/blade/addon/features/other/SwingAnimation.java
new file mode 100644
index 0000000..4c21bf7
--- /dev/null
+++ b/src/main/java/blade/addon/features/other/SwingAnimation.java
@@ -0,0 +1,30 @@
+package blade.addon.features.other;
+
+public class SwingAnimation {
+
+    private static int ignoreCount = 0;
+
+    /**
+     * tells consumeSwing to ignore next swing
+     */
+    public static void ignoreNext(int count) {
+        ignoreCount = count;
+    }
+
+    /**
+     * Consumes a swing and checks if it should ignore swing rendering
+     */
+    public static void consumeSwing() {
+        if (ignoreCount > 0) {
+            ignoreCount--;
+        }
+    }
+
+    /**
+     * Checks if it should ignore the swing
+     * @return true if it should ignore the swing animation, else false
+     */
+    public static boolean shouldIgnore() {
+        return ignoreCount > 0;
+    }
+}
diff --git a/src/main/java/blade/addon/mixin/EnderPearlItemMixin.java b/src/main/java/blade/addon/mixin/EnderPearlItemMixin.java
new file mode 100644
index 0000000..7a78cdf
--- /dev/null
+++ b/src/main/java/blade/addon/mixin/EnderPearlItemMixin.java
@@ -0,0 +1,24 @@
+package blade.addon.mixin;
+
+import blade.addon.features.other.SwingAnimation;
+import blade.addon.utils.config.values.Visual;
+import blade.addon.utils.data.EntityUtil;
+import net.minecraft.entity.player.PlayerEntity;
+import net.minecraft.item.EnderPearlItem;
+import net.minecraft.util.Hand;
+import net.minecraft.world.World;
+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.CallbackInfoReturnable;
+
+@Mixin(EnderPearlItem.class)
+public class EnderPearlItemMixin {
+
+    @Inject(method = "use", at=@At("HEAD"))
+    private void use(World world, PlayerEntity user, Hand hand, CallbackInfoReturnable<Boolean> cir) {
+        if (Visual.stopPearlSwing && EntityUtil.isClientPlayer(user)) {
+            SwingAnimation.ignoreNext(2);
+        }
+    }
+}
diff --git a/src/main/java/blade/addon/mixin/HeldItemRendererMixin.java b/src/main/java/blade/addon/mixin/HeldItemRendererMixin.java
index c75ce2e..b4d68d9 100644
--- a/src/main/java/blade/addon/mixin/HeldItemRendererMixin.java
+++ b/src/main/java/blade/addon/mixin/HeldItemRendererMixin.java
@@ -1,6 +1,7 @@
 package blade.addon.mixin;
 
 import blade.addon.features.item.DropAnimation;
+import blade.addon.features.other.SwingAnimation;
 import com.llamalad7.mixinextras.sugar.Local;
 import net.minecraft.client.network.ClientPlayerEntity;
 import net.minecraft.client.render.item.HeldItemRenderer;
@@ -54,9 +55,15 @@ public abstract class HeldItemRendererMixin {
 
     @Inject(method = "swingArm", at = @At("HEAD"), cancellable = true)
     private void stopSwing(float swingProgress, float equipProgress, MatrixStack matrices, int armX, Arm arm, CallbackInfo ci) {
-        if (!DropAnimation.shouldProceed()) return;
-        ItemStack prevDropped = DropAnimation.getPrevDroppedItem();
-        if (prevDropped == null) return;
+
+        //TODO: refactor this
+        if (!SwingAnimation.shouldIgnore()) {
+            if (!DropAnimation.shouldProceed()) return;
+            ItemStack prevDropped = DropAnimation.getPrevDroppedItem();
+            if (prevDropped == null) return;
+        }
+
+
         int i = arm == Arm.RIGHT ? 1 : -1;
         matrices.translate((float) i * 0.56F, -0.52F, -0.72F);
         ci.cancel();
diff --git a/src/main/java/blade/addon/mixin/LivingEntityMixin.java b/src/main/java/blade/addon/mixin/LivingEntityMixin.java
new file mode 100644
index 0000000..bf534ad
--- /dev/null
+++ b/src/main/java/blade/addon/mixin/LivingEntityMixin.java
@@ -0,0 +1,23 @@
+package blade.addon.mixin;
+
+import blade.addon.features.other.SwingAnimation;
+import blade.addon.utils.data.EntityUtil;
+import net.minecraft.entity.Entity;
+import net.minecraft.entity.LivingEntity;
+import net.minecraft.util.Hand;
+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;
+
+@Mixin(LivingEntity.class)
+public class LivingEntityMixin {
+
+
+    @Inject(method = "swingHand(Lnet/minecraft/util/Hand;Z)V", at = @At("TAIL"))
+    public void swingHand(Hand hand, boolean fromServerPlayer, CallbackInfo ci) {
+        if (EntityUtil.isClientPlayer((Entity) (Object) this)) {
+            SwingAnimation.consumeSwing();
+        }
+    }
+}
diff --git a/src/main/java/blade/addon/mixin/ShovelItemMixin.java b/src/main/java/blade/addon/mixin/ShovelItemMixin.java
new file mode 100644
index 0000000..9838afc
--- /dev/null
+++ b/src/main/java/blade/addon/mixin/ShovelItemMixin.java
@@ -0,0 +1,23 @@
+package blade.addon.mixin;
+
+import blade.addon.features.other.SwingAnimation;
+import blade.addon.utils.config.values.Visual;
+import blade.addon.utils.data.EntityUtil;
+import net.minecraft.item.ItemUsageContext;
+import net.minecraft.item.ShovelItem;
+import net.minecraft.util.ActionResult;
+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.CallbackInfoReturnable;
+
+@Mixin(ShovelItem.class)
+public class ShovelItemMixin {
+
+    @Inject(method = "useOnBlock", at= @At(value = "INVOKE", target = "Lnet/minecraft/world/World;playSound(Lnet/minecraft/entity/Entity;Lnet/minecraft/util/math/BlockPos;Lnet/minecraft/sound/SoundEvent;Lnet/minecraft/sound/SoundCategory;FF)V"))
+    public void useOnBlock(ItemUsageContext context, CallbackInfoReturnable<ActionResult> cir) {
+        if (Visual.stopShovelFlattening && EntityUtil.isClientPlayer(context.getPlayer())) {
+            SwingAnimation.ignoreNext(2);
+        }
+    }
+}
diff --git a/src/main/java/blade/addon/utils/config/Config.java b/src/main/java/blade/addon/utils/config/Config.java
index df75971..dbbf5fc 100644
--- a/src/main/java/blade/addon/utils/config/Config.java
+++ b/src/main/java/blade/addon/utils/config/Config.java
@@ -395,6 +395,8 @@ public class Config {
         visual.add(new ConfigBool(Text.literal("Old player head size"), () -> Visual.oldPlayerHead, bool -> Visual.oldPlayerHead = bool));
         visual.add(new ConfigBool(Text.literal("Fix wither essence"), () -> Visual.fixWitherEssence, bool -> Visual.fixWitherEssence = bool));
         visual.add(new ConfigBool(Text.literal("1.8.9 like fishing bobber"), () -> Visual.oldFishingRod, bool -> Visual.oldFishingRod = bool));
+        visual.add(new ConfigBool(Text.literal("Stop shovel swing when flattening dirt"), () -> Visual.stopShovelFlattening, bool -> Visual.stopShovelFlattening = bool));
+        visual.add(new ConfigBool(Text.literal("Stop swinging when throwing a pearl"), () -> Visual.stopPearlSwing, bool -> Visual.stopPearlSwing = bool));
         return visual;
     }
 
diff --git a/src/main/java/blade/addon/utils/config/values/Visual.java b/src/main/java/blade/addon/utils/config/values/Visual.java
index 3ef7fc4..917a208 100644
--- a/src/main/java/blade/addon/utils/config/values/Visual.java
+++ b/src/main/java/blade/addon/utils/config/values/Visual.java
@@ -46,4 +46,10 @@ public class Visual {
     @ConfigValue
     public static boolean oldFishingRod = false;
 
+    @ConfigValue
+    public static boolean stopShovelFlattening = false;
+
+    @ConfigValue
+    public static boolean stopPearlSwing = false;
+
 }
diff --git a/src/main/resources/blade-addons.mixins.json b/src/main/resources/blade-addons.mixins.json
index b8b9a31..0087730 100644
--- a/src/main/resources/blade-addons.mixins.json
+++ b/src/main/resources/blade-addons.mixins.json
@@ -6,9 +6,12 @@
     "ChunkMixin",
     "ClientConnectionMixin",
     "ClientPlayNetworkHandlerMixin",
+    "EnderPearlItemMixin",
     "EntityMixin",
     "ItemStackMixin",
+    "LivingEntityMixin",
     "PlayerInventoryMixin",
+    "ShovelItemMixin",
     "StuckArrowsFeatureRendererMixin"
   ],
   "injectors": {
refactor: incomplete 1.21.11 implementationSteinGaming · 2026-02-19 · 38849ee
查看变更
gradle.properties                                                            |  8 ++++----
 gradlew                                                                      |  0
 src/main/java/blade/addon/features/dungeon/ItemHighlight.java                |  5 ++++-
 src/main/java/blade/addon/features/dungeon/f7/RelicTimer.java                |  5 ++++-
 src/main/java/blade/addon/features/dungeon/f7/location/PositionMessages.java |  7 +++++--
 src/main/java/blade/addon/mixin/HandledScreenMixin.java                      |  4 ++--
 src/main/java/blade/addon/mixin/HeldItemRendererMixin.java                   |  2 +-
 src/main/java/blade/addon/mixin/StatusEffectsDisplayMixin.java               | 11 ++++++++---
 src/main/java/blade/addon/utils/Waypoint.java                                |  6 +++++-
 src/main/java/blade/addon/utils/rendering/RenderLayers.java                  | 37 ++++++++++++++-----------------------
 src/main/java/blade/addon/utils/rendering/RenderUtils.java                   |  9 +++++++--
 src/main/java/blade/addon/utils/rendering/RenderingEvents.java               | 21 ++++++++++++++++-----
 12 files changed, 70 insertions(+), 45 deletions(-)
diff --git a/gradle.properties b/gradle.properties
index 11c2c78..22d4087 100644
--- a/gradle.properties
+++ b/gradle.properties
@@ -7,8 +7,8 @@ org.gradle.configuration-cache=false
 
 # Fabric Properties
 # check these on https://fabricmc.net/develop
-minecraft_version=1.21.10
-yarn_mappings=1.21.10+build.3
+minecraft_version=1.21.11
+yarn_mappings=1.21.11+build.4
 loader_version=0.18.4
 loom_version=1.15-SNAPSHOT
 
@@ -18,8 +18,8 @@ maven_group=blade.addon
 archives_base_name=blade-addons
 
 # Dependencies
-fabric_version=0.138.4+1.21.10
+fabric_version=0.141.3+1.21.11
 # I love dependencies sometimes breaking
 practical_config=1.4.3
 modmenu_version = 16.0.0-rc.2
-hypixel_api_version = 1.0.1
\ No newline at end of file
+hypixel_api_version = 1.0.1
diff --git a/gradlew b/gradlew
old mode 100644
new mode 100755
diff --git a/src/main/java/blade/addon/features/dungeon/ItemHighlight.java b/src/main/java/blade/addon/features/dungeon/ItemHighlight.java
index 286f8c7..0050920 100644
--- a/src/main/java/blade/addon/features/dungeon/ItemHighlight.java
+++ b/src/main/java/blade/addon/features/dungeon/ItemHighlight.java
@@ -11,6 +11,7 @@ import net.fabricmc.fabric.api.client.event.lifecycle.v1.ClientTickEvents;
 import net.fabricmc.fabric.api.client.rendering.v1.world.WorldRenderContext;
 import net.minecraft.client.MinecraftClient;
 import net.minecraft.client.network.ClientPlayerEntity;
+import net.minecraft.client.render.DrawStyle;
 import net.minecraft.client.render.VertexConsumer;
 import net.minecraft.client.render.VertexRendering;
 import net.minecraft.client.util.math.MatrixStack;
@@ -19,7 +20,9 @@ import net.minecraft.entity.EntityDimensions;
 import net.minecraft.entity.ItemEntity;
 import net.minecraft.text.Text;
 import net.minecraft.util.math.Box;
+import net.minecraft.util.math.ColorHelper;
 import net.minecraft.util.math.MathHelper;
+import net.minecraft.world.debug.gizmo.GizmoDrawing;
 
 import java.util.HashSet;
 import java.util.List;
@@ -71,7 +74,7 @@ public class ItemHighlight {
             EntityDimensions dimension = itemEntity.getDimensions(itemEntity.getPose());
             Box box = dimension.getBoxAt(x, y, z).expand(0.1).offset(0, 0.05, 0);
             float[] color = RenderUtils.toFloats(getColor(itemEntity));
-            VertexRendering.drawFilledBox(matrixStack, consumer, box.minX, box.minY, box.minZ, box.maxX, box.maxY, box.maxZ, color[0], color[1], color[2], color[3]);
+            GizmoDrawing.box(box, DrawStyle.filled(ColorHelper.fromFloats(color[3], color[0], color[1], color[2])));
         });
     }
 
diff --git a/src/main/java/blade/addon/features/dungeon/f7/RelicTimer.java b/src/main/java/blade/addon/features/dungeon/f7/RelicTimer.java
index 34ad813..f6f1237 100644
--- a/src/main/java/blade/addon/features/dungeon/f7/RelicTimer.java
+++ b/src/main/java/blade/addon/features/dungeon/f7/RelicTimer.java
@@ -17,6 +17,7 @@ import net.fabricmc.fabric.api.client.event.lifecycle.v1.ClientTickEvents;
 import net.fabricmc.fabric.api.client.rendering.v1.world.WorldRenderContext;
 import net.minecraft.client.MinecraftClient;
 import net.minecraft.client.gui.DrawContext;
+import net.minecraft.client.render.DrawStyle;
 import net.minecraft.client.render.VertexConsumer;
 import net.minecraft.client.render.VertexRendering;
 import net.minecraft.client.util.math.MatrixStack;
@@ -29,6 +30,8 @@ import net.minecraft.util.Formatting;
 import net.minecraft.util.hit.BlockHitResult;
 import net.minecraft.util.math.BlockPos;
 import net.minecraft.util.math.Box;
+import net.minecraft.util.math.ColorHelper;
+import net.minecraft.world.debug.gizmo.GizmoDrawing;
 
 import java.util.regex.Matcher;
 import java.util.regex.Pattern;
@@ -263,7 +266,7 @@ public class RelicTimer {
 
         Box box = pickedupRelic.box;
         float[] color = RenderUtils.toFloats(pickedupRelic.color);
-        VertexRendering.drawFilledBox(matrixStack, consumer, box.minX, box.minY, box.minZ, box.maxX, box.maxY, box.maxZ, color[0], color[1], color[2], color[3]);
+        GizmoDrawing.box(box, DrawStyle.filled(ColorHelper.fromFloats(color[3], color[0], color[1], color[2])));
     }
 
     public static boolean display() {
diff --git a/src/main/java/blade/addon/features/dungeon/f7/location/PositionMessages.java b/src/main/java/blade/addon/features/dungeon/f7/location/PositionMessages.java
index 3702eed..d0d2680 100644
--- a/src/main/java/blade/addon/features/dungeon/f7/location/PositionMessages.java
+++ b/src/main/java/blade/addon/features/dungeon/f7/location/PositionMessages.java
@@ -10,10 +10,13 @@ import net.fabricmc.fabric.api.client.event.lifecycle.v1.ClientTickEvents;
 import net.fabricmc.fabric.api.client.rendering.v1.world.WorldRenderContext;
 import net.minecraft.client.MinecraftClient;
 import net.minecraft.client.network.ClientPlayerEntity;
+import net.minecraft.client.render.DrawStyle;
 import net.minecraft.client.render.VertexConsumer;
 import net.minecraft.client.render.VertexRendering;
 import net.minecraft.client.util.math.MatrixStack;
 import net.minecraft.util.math.Box;
+import net.minecraft.util.math.ColorHelper;
+import net.minecraft.world.debug.gizmo.GizmoDrawing;
 
 import java.util.ArrayList;
 
@@ -103,9 +106,9 @@ public class PositionMessages {
         for (PositionMessage positionMessage : positionMessages) {
             Box box = positionMessage.getBox();
             if (positionMessage.sent()) {
-                VertexRendering.drawBox(entry, consumer, box, 0, 1, 0, 1);
+                GizmoDrawing.box(box, DrawStyle.stroked(ColorHelper.fromFloats(1.0f, 0, 1, 0)));
             } else {
-                VertexRendering.drawBox(entry, consumer, box, 1, 0, 0, 1);
+                GizmoDrawing.box(box, DrawStyle.stroked(ColorHelper.fromFloats(1.0f, 1, 0, 0)));
             }
         }
     }
diff --git a/src/main/java/blade/addon/mixin/HandledScreenMixin.java b/src/main/java/blade/addon/mixin/HandledScreenMixin.java
index 1b05bca..22485ce 100644
--- a/src/main/java/blade/addon/mixin/HandledScreenMixin.java
+++ b/src/main/java/blade/addon/mixin/HandledScreenMixin.java
@@ -41,7 +41,7 @@ public abstract class HandledScreenMixin<T extends ScreenHandler> extends Screen
     }
 
     @Inject(method = "drawSlot", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/gui/DrawContext;drawItem(Lnet/minecraft/item/ItemStack;III)V"))
-    public void drawBackground(DrawContext context, Slot slot, CallbackInfo ci) {
+    public void drawBackground(DrawContext context, Slot slot, int mouseX, int mouseY, CallbackInfo ci) {
         int x = slot.x;
         int y = slot.y;
         ItemStack stack = slot.getStack();
@@ -49,7 +49,7 @@ public abstract class HandledScreenMixin<T extends ScreenHandler> extends Screen
     }
 
     @Inject(method = "drawSlot", at = @At(value = "TAIL"))
-    public void drawStarCount(DrawContext context, Slot slot, CallbackInfo ci) {
+    public void drawStarCount(DrawContext context, Slot slot, int mouseX, int mouseY, CallbackInfo ci) {
         int x = slot.x;
         int y = slot.y;
         ItemStack stack = slot.getStack();
diff --git a/src/main/java/blade/addon/mixin/HeldItemRendererMixin.java b/src/main/java/blade/addon/mixin/HeldItemRendererMixin.java
index c75ce2e..5635985 100644
--- a/src/main/java/blade/addon/mixin/HeldItemRendererMixin.java
+++ b/src/main/java/blade/addon/mixin/HeldItemRendererMixin.java
@@ -53,7 +53,7 @@ public abstract class HeldItemRendererMixin {
     }
 
     @Inject(method = "swingArm", at = @At("HEAD"), cancellable = true)
-    private void stopSwing(float swingProgress, float equipProgress, MatrixStack matrices, int armX, Arm arm, CallbackInfo ci) {
+    private void stopSwing(float swingProgress, MatrixStack matrices, int armX, Arm arm, CallbackInfo ci) {
         if (!DropAnimation.shouldProceed()) return;
         ItemStack prevDropped = DropAnimation.getPrevDroppedItem();
         if (prevDropped == null) return;
diff --git a/src/main/java/blade/addon/mixin/StatusEffectsDisplayMixin.java b/src/main/java/blade/addon/mixin/StatusEffectsDisplayMixin.java
index 34d76ef..26b17dc 100644
--- a/src/main/java/blade/addon/mixin/StatusEffectsDisplayMixin.java
+++ b/src/main/java/blade/addon/mixin/StatusEffectsDisplayMixin.java
@@ -1,25 +1,30 @@
 package blade.addon.mixin;
 
 import blade.addon.utils.config.values.Visual;
+import net.minecraft.client.font.TextRenderer;
 import net.minecraft.client.gui.DrawContext;
 import net.minecraft.client.gui.screen.ingame.StatusEffectsDisplay;
+import net.minecraft.entity.effect.StatusEffectInstance;
+import net.minecraft.text.Text;
 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 java.util.Collection;
+
 @Mixin(StatusEffectsDisplay.class)
 public class StatusEffectsDisplayMixin {
 
     @Inject(method = "drawStatusEffects", at=@At("HEAD"), cancellable = true)
-    public void drawEffects(DrawContext context, int mouseX, int mouseY, CallbackInfo ci) {
+    public void drawEffects(DrawContext context, Collection<StatusEffectInstance> effects, int x, int height, int mouseX, int mouseY, int width, CallbackInfo ci) {
         if (Visual.hideStatusOverLay) {
             ci.cancel();
         }
     }
 
-    @Inject(method = "drawStatusEffectTooltip", at=@At("HEAD"), cancellable = true)
-    public void drawToolTip(DrawContext context, int mouseX, int mouseY, CallbackInfo ci) {
+    @Inject(method = "drawTexts", at=@At("HEAD"), cancellable = true)
+    public void drawToolTip(CallbackInfo ci) {
         if (Visual.hideStatusOverLay) {
             ci.cancel();
         }
diff --git a/src/main/java/blade/addon/utils/Waypoint.java b/src/main/java/blade/addon/utils/Waypoint.java
index 36688cb..ac6d0d6 100644
--- a/src/main/java/blade/addon/utils/Waypoint.java
+++ b/src/main/java/blade/addon/utils/Waypoint.java
@@ -1,8 +1,12 @@
 package blade.addon.utils;
 
+import net.minecraft.client.render.DrawStyle;
 import net.minecraft.client.render.VertexConsumer;
 import net.minecraft.client.render.VertexRendering;
 import net.minecraft.client.util.math.MatrixStack;
+import net.minecraft.util.math.Box;
+import net.minecraft.util.math.ColorHelper;
+import net.minecraft.world.debug.gizmo.GizmoDrawing;
 
 public class Waypoint {
     private final double x, y, z, dx, dy, dz;
@@ -38,7 +42,7 @@ public class Waypoint {
     }
 
     public void Render(VertexConsumer consumer, MatrixStack matrixStack) {
-        VertexRendering.drawFilledBox(matrixStack, consumer, x, y, z, x + dx, y + dy, z + dz, r, g, b, a);
+        GizmoDrawing.box(new Box(x, y, z, x + dx, y + dy, z + dz), DrawStyle.filled(ColorHelper.fromFloats(a, r, g, b)));
     }
 
     @Override
diff --git a/src/main/java/blade/addon/utils/rendering/RenderLayers.java b/src/main/java/blade/addon/utils/rendering/RenderLayers.java
index a4ce157..f577f31 100644
--- a/src/main/java/blade/addon/utils/rendering/RenderLayers.java
+++ b/src/main/java/blade/addon/utils/rendering/RenderLayers.java
@@ -1,7 +1,8 @@
 package blade.addon.utils.rendering;
 
+import net.minecraft.client.render.LayeringTransform;
 import net.minecraft.client.render.RenderLayer;
-import net.minecraft.client.render.RenderPhase;
+import net.minecraft.client.render.RenderSetup;
 
 import java.util.HashMap;
 import java.util.OptionalDouble;
@@ -11,20 +12,15 @@ public class RenderLayers {
     private static final HashMap<Integer, RenderLayer> OUTLINE_WIDTHS = new HashMap<>();
     private static final HashMap<Integer, RenderLayer> OUTLINE_WIDTHS_NO_DEPTH = new HashMap<>();
 
-    public static final RenderLayer.MultiPhase FILLED_LAYER =
-            RenderLayer.of("filled", RenderLayer.DEFAULT_BUFFER_SIZE, false, true, RenderPipelines.FILLED_PIPELINE, RenderLayer.MultiPhaseParameters.builder()
-                    .layering(RenderPhase.VIEW_OFFSET_Z_LAYERING)
-                    .build(false));
+    public static final RenderLayer FILLED_LAYER =
+            RenderLayer.of("filled", RenderSetup.builder(RenderPipelines.FILLED_PIPELINE).layeringTransform(LayeringTransform.VIEW_OFFSET_Z_LAYERING).build());
 
-    public static final RenderLayer.MultiPhase FILLED_LAYER_NO_DEPTH =
-            RenderLayer.of("through_wall_filled", RenderLayer.DEFAULT_BUFFER_SIZE, false, true, RenderPipelines.FILLED_PIPELINE_NO_DEPTH, RenderLayer.MultiPhaseParameters.builder()
-                    .layering(RenderPhase.VIEW_OFFSET_Z_LAYERING)
-                    .build(false));
 
-    public static final RenderLayer.MultiPhase FILLED_ENTITY_LAYER =
-            RenderLayer.of("filled-entity", RenderLayer.DEFAULT_BUFFER_SIZE, false, true, RenderPipelines.FILLED_ENTITY_PIPELINE, RenderLayer.MultiPhaseParameters.builder()
-                    .layering(RenderPhase.VIEW_OFFSET_Z_LAYERING)
-                    .build(false));
+    public static final RenderLayer FILLED_LAYER_NO_DEPTH =
+            RenderLayer.of("through_wall_filled", RenderSetup.builder(RenderPipelines.FILLED_PIPELINE_NO_DEPTH).layeringTransform(LayeringTransform.VIEW_OFFSET_Z_LAYERING).build());
+
+    public static final RenderLayer FILLED_ENTITY_LAYER =
+            RenderLayer.of("filled-entity", RenderSetup.builder(RenderPipelines.FILLED_ENTITY_PIPELINE).layeringTransform(LayeringTransform.VIEW_OFFSET_Z_LAYERING).build());
 
     public static RenderLayer getOutline(int width, boolean depthCheck) {
         if (depthCheck) {
@@ -32,11 +28,8 @@ public class RenderLayers {
                 return OUTLINE_WIDTHS.get(width);
             }
 
-            RenderLayer.MultiPhase outline_layer =
-                    RenderLayer.of("outline-entity-" + width, RenderLayer.DEFAULT_BUFFER_SIZE, false, true, RenderPipelines.OUTLINE_ENTITY_PIPELINE, RenderLayer.MultiPhaseParameters.builder()
-                            .layering(RenderPhase.VIEW_OFFSET_Z_LAYERING)
-                            .lineWidth(new RenderPhase.LineWidth(OptionalDouble.of(width)))
-                            .build(false));
+            RenderLayer outline_layer =
+                    RenderLayer.of("outline-entity-" + width, RenderSetup.builder(RenderPipelines.OUTLINE_ENTITY_PIPELINE).outlineMode(RenderSetup.OutlineMode.IS_OUTLINE).layeringTransform(LayeringTransform.VIEW_OFFSET_Z_LAYERING).build());
 
             OUTLINE_WIDTHS.put(width, outline_layer);
             return outline_layer;
@@ -45,11 +38,9 @@ public class RenderLayers {
                 return OUTLINE_WIDTHS_NO_DEPTH.get(width);
             }
 
-            RenderLayer.MultiPhase outline_layer =
-                    RenderLayer.of("outline-entity-" + width, RenderLayer.DEFAULT_BUFFER_SIZE, false, true, RenderPipelines.OUTLINE_ENTITY_PIPELINE_NO_DEPTH, RenderLayer.MultiPhaseParameters.builder()
-                            .layering(RenderPhase.VIEW_OFFSET_Z_LAYERING)
-                            .lineWidth(new RenderPhase.LineWidth(OptionalDouble.of(width)))
-                            .build(false));
+            RenderLayer outline_layer =
+                    RenderLayer.of("outline-entity-" + width, RenderSetup.builder(RenderPipelines.OUTLINE_ENTITY_PIPELINE).outlineMode(RenderSetup.OutlineMode.IS_OUTLINE).layeringTransform(LayeringTransform.VIEW_OFFSET_Z_LAYERING).build());
+
 
             OUTLINE_WIDTHS_NO_DEPTH.put(width, outline_layer);
             return outline_layer;
diff --git a/src/main/java/blade/addon/utils/rendering/RenderUtils.java b/src/main/java/blade/addon/utils/rendering/RenderUtils.java
index 3c850b7..c201fcc 100644
--- a/src/main/java/blade/addon/utils/rendering/RenderUtils.java
+++ b/src/main/java/blade/addon/utils/rendering/RenderUtils.java
@@ -9,12 +9,15 @@ import net.minecraft.client.MinecraftClient;
 import net.minecraft.client.font.TextRenderer;
 import net.minecraft.client.gui.DrawContext;
 import net.minecraft.client.network.ClientPlayerEntity;
+import net.minecraft.client.render.DrawStyle;
 import net.minecraft.client.render.VertexConsumer;
 import net.minecraft.client.render.VertexRendering;
 import net.minecraft.client.util.math.MatrixStack;
 import net.minecraft.text.Text;
 import net.minecraft.util.math.Box;
+import net.minecraft.util.math.ColorHelper;
 import net.minecraft.util.math.Vec3d;
+import net.minecraft.world.debug.gizmo.GizmoDrawing;
 
 public class RenderUtils {
 
@@ -87,12 +90,14 @@ public class RenderUtils {
 
     public static void renderFilled(MatrixStack matrixStack, VertexConsumer consumer, Box box, float[] rgba) {
         if (rgba[3] == 0) return;
-        VertexRendering.drawFilledBox(matrixStack, consumer, box.minX, box.minY, box.minZ, box.maxX, box.maxY, box.maxZ, rgba[0], rgba[1], rgba[2], rgba[3]);
+        GizmoDrawing.box(box, DrawStyle.filled(ColorHelper.fromFloats(rgba[3], rgba[0], rgba[1], rgba[2]))); // Could be filledAndStroked
+        //VertexRendering.drawFilledBox(matrixStack, consumer, box.minX, box.minY, box.minZ, box.maxX, box.maxY, box.maxZ, rgba[0], rgba[1], rgba[2], rgba[3]);
     }
 
     public static void renderOutline(MatrixStack matrixStack, VertexConsumer consumer, Box box, float[] rgba) {
         if (rgba[3] == 0) return;
-        VertexRendering.drawBox(matrixStack.peek(), consumer, box, rgba[0], rgba[1], rgba[2], rgba[3]);
+        GizmoDrawing.box(box, DrawStyle.stroked(ColorHelper.fromFloats(rgba[3], rgba[0], rgba[1], rgba[2]))); // Could be filledAndStroked
+        //VertexRendering.drawBox(matrixStack.peek(), consumer, box, rgba[0], rgba[1], rgba[2], rgba[3]);
     }
 
 
diff --git a/src/main/java/blade/addon/utils/rendering/RenderingEvents.java b/src/main/java/blade/addon/utils/rendering/RenderingEvents.java
index b5147d6..9ca160b 100644
--- a/src/main/java/blade/addon/utils/rendering/RenderingEvents.java
+++ b/src/main/java/blade/addon/utils/rendering/RenderingEvents.java
@@ -5,6 +5,7 @@ import net.fabricmc.fabric.api.client.rendering.v1.world.WorldRenderContext;
 import net.fabricmc.fabric.api.client.rendering.v1.world.WorldRenderEvents;
 import net.minecraft.client.render.VertexConsumer;
 import net.minecraft.client.render.VertexConsumerProvider;
+import net.minecraft.client.render.state.WorldRenderState;
 import net.minecraft.client.util.math.MatrixStack;
 import net.minecraft.util.math.Vec3d;
 
@@ -25,7 +26,9 @@ public class RenderingEvents {
     }
 
     private static void filled(WorldRenderContext context) {
-        Vec3d camera = context.worldState().cameraRenderState.pos;
+        WorldRenderState worldState = context.worldState();
+        if (worldState == null) return;
+        Vec3d camera = worldState.cameraRenderState.pos;
         MatrixStack matrices = context.matrices();
         if (matrices == null) return;
         matrices.push();
@@ -40,7 +43,9 @@ public class RenderingEvents {
     }
 
     private static void filledNoDepth(WorldRenderContext context) {
-        Vec3d camera = context.worldState().cameraRenderState.pos;
+        WorldRenderState worldState = context.worldState();
+        if (worldState == null) return;
+        Vec3d camera = worldState.cameraRenderState.pos;
         MatrixStack matrices = context.matrices();
         if (matrices == null) return;
         matrices.push();
@@ -55,7 +60,9 @@ public class RenderingEvents {
     }
 
     private static void entityFilled(WorldRenderContext context) {
-        Vec3d camera = context.worldState().cameraRenderState.pos;
+        WorldRenderState worldState = context.worldState();
+        if (worldState == null) return;
+        Vec3d camera = worldState.cameraRenderState.pos;
         MatrixStack matrices = context.matrices();
         if (matrices == null) return;
         matrices.push();
@@ -71,7 +78,9 @@ public class RenderingEvents {
 
 
     private static void entityOutline(WorldRenderContext context) {
-        Vec3d camera = context.worldState().cameraRenderState.pos;
+        WorldRenderState worldState = context.worldState();
+        if (worldState == null) return;
+        Vec3d camera = worldState.cameraRenderState.pos;
         MatrixStack matrices = context.matrices();
         if (matrices == null) return;
         matrices.push();
@@ -86,7 +95,9 @@ public class RenderingEvents {
     }
 
     private static void entityOutlineNoDepth(WorldRenderContext context) {
-        Vec3d camera = context.worldState().cameraRenderState.pos;
+        WorldRenderState worldState = context.worldState();
+        if (worldState == null) return;
+        Vec3d camera = worldState.cameraRenderState.pos;
         MatrixStack matrices = context.matrices();
         if (matrices == null) return;
         matrices.push();
- increased the max char limit from 100 -> 200 in filter - added math parser to the searchbar, works p much the same as neus unless ive missed smthGabriel Ellnebrand · 2026-02-19 · d365b8b
查看变更
src/main/java/blade/addon/features/dungeon/ItemHighlight.java            |   2 +-
 src/main/java/blade/addon/features/dungeon/SecretSpawnTimer.java         |   2 +-
 src/main/java/blade/addon/features/dungeon/f7/dragons/DragonHealth.java  |   2 +-
 src/main/java/blade/addon/features/dungeon/f7/terms/GoldorTickTimer.java |   2 +-
 src/main/java/blade/addon/features/filter/FilterEntry.java               |   2 +-
 src/main/java/blade/addon/features/other/PracticeSS.java                 |   2 +-
 src/main/java/blade/addon/features/other/SearchBar.java                  |  20 +++++-
 src/main/java/blade/addon/mixin/BossBarHudMixin.java                     |   2 +-
 src/main/java/blade/addon/utils/Constants.java                           |  16 +++--
 src/main/java/blade/addon/utils/MathParser.java                          | 306 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 src/main/java/blade/addon/utils/config/Config.java                       |   2 +-
 src/main/java/blade/addon/utils/rendering/RenderUtils.java               |  22 +++---
 12 files changed, 354 insertions(+), 26 deletions(-)
diff --git a/src/main/java/blade/addon/features/dungeon/ItemHighlight.java b/src/main/java/blade/addon/features/dungeon/ItemHighlight.java
index c257f9a..286f8c7 100644
--- a/src/main/java/blade/addon/features/dungeon/ItemHighlight.java
+++ b/src/main/java/blade/addon/features/dungeon/ItemHighlight.java
@@ -96,7 +96,7 @@ public class ItemHighlight {
         } else if (item.age > 11){
             return Constants.GREEN;
         } else {
-            return Constants.ORANGE;
+            return Constants.GOLD;
         }
     }
 
diff --git a/src/main/java/blade/addon/features/dungeon/SecretSpawnTimer.java b/src/main/java/blade/addon/features/dungeon/SecretSpawnTimer.java
index 6032314..d6e1c83 100644
--- a/src/main/java/blade/addon/features/dungeon/SecretSpawnTimer.java
+++ b/src/main/java/blade/addon/features/dungeon/SecretSpawnTimer.java
@@ -46,7 +46,7 @@ public class SecretSpawnTimer {
         int x = component.getScaledX();
         int y = component.getScaledY();
 
-        int color = tick > 10 ? Constants.GREEN : tick > 5 ? Constants.ORANGE : Constants.RED;
+        int color = tick > 10 ? Constants.GREEN : tick > 5 ? Constants.GOLD : Constants.RED;
 
         RenderUtils.drawCenteredText(context, MinecraftClient.getInstance().textRenderer, Text.literal(tick + ""), x, y, component.getWidth(), color);
 
diff --git a/src/main/java/blade/addon/features/dungeon/f7/dragons/DragonHealth.java b/src/main/java/blade/addon/features/dungeon/f7/dragons/DragonHealth.java
index fbfd28a..8c3026e 100644
--- a/src/main/java/blade/addon/features/dungeon/f7/dragons/DragonHealth.java
+++ b/src/main/java/blade/addon/features/dungeon/f7/dragons/DragonHealth.java
@@ -78,7 +78,7 @@ public class DragonHealth {
 
             if (health == 0) return;
             Vec3d pos = EntityUtil.getLerpedPos(dragon);
-            RenderUtils.renderText(context, matrixStack, Text.literal(RenderUtils.formatHealth(health)).withColor(getColor(health)), pos, 5);
+            RenderUtils.renderText(context, matrixStack, Text.literal(RenderUtils.formatNumber(health)).withColor(getColor(health)), pos, 5);
 
         });
 
diff --git a/src/main/java/blade/addon/features/dungeon/f7/terms/GoldorTickTimer.java b/src/main/java/blade/addon/features/dungeon/f7/terms/GoldorTickTimer.java
index 8819139..29fa7ac 100644
--- a/src/main/java/blade/addon/features/dungeon/f7/terms/GoldorTickTimer.java
+++ b/src/main/java/blade/addon/features/dungeon/f7/terms/GoldorTickTimer.java
@@ -37,7 +37,7 @@ public class GoldorTickTimer {
         if (Floor7.inDeathTicks && !Floor7.makeGoldorTickUp) mod = 3.0 - mod;
         if (Floor7.inDeathTicks) num = mod;
 
-        int color = (mod < 1 ? Constants.GREEN : mod < 2 ? Constants.ORANGE : Constants.RED);
+        int color = (mod < 1 ? Constants.GREEN : mod < 2 ? Constants.GOLD : Constants.RED);
 
         RenderUtils.drawTimer(component, context, num, color);
     }
diff --git a/src/main/java/blade/addon/features/filter/FilterEntry.java b/src/main/java/blade/addon/features/filter/FilterEntry.java
index 7482af2..efee0a2 100644
--- a/src/main/java/blade/addon/features/filter/FilterEntry.java
+++ b/src/main/java/blade/addon/features/filter/FilterEntry.java
@@ -34,7 +34,7 @@ public class FilterEntry extends TextFieldWidget {
 
     public FilterEntry(Supplier<String> supplier, Consumer<String> consumer, int index, FilterList parent) {
         super(MinecraftClient.getInstance().textRenderer, Constants.WIDGET_WIDTH, HEIGHT, Text.empty());
-        this.setMaxLength(100);
+        this.setMaxLength(200);
         this.setText(supplier.get());
         this.setChangedListener(consumer);
         this.index = index;
diff --git a/src/main/java/blade/addon/features/other/PracticeSS.java b/src/main/java/blade/addon/features/other/PracticeSS.java
index c127203..4380ae4 100644
--- a/src/main/java/blade/addon/features/other/PracticeSS.java
+++ b/src/main/java/blade/addon/features/other/PracticeSS.java
@@ -304,7 +304,7 @@ public class PracticeSS {
     private static int getColor(int index) {
         if (index == 1 && ticksSinceLuckyButton > 0) return ExtraOptions.luckyButtonColor;
         if (index == currentIndex) return Constants.GREEN;
-        if (index == currentIndex + 1) return Constants.ORANGE;
+        if (index == currentIndex + 1) return Constants.GOLD;
         return Constants.RED;
     }
 
diff --git a/src/main/java/blade/addon/features/other/SearchBar.java b/src/main/java/blade/addon/features/other/SearchBar.java
index 1ce04d8..d6f6c01 100644
--- a/src/main/java/blade/addon/features/other/SearchBar.java
+++ b/src/main/java/blade/addon/features/other/SearchBar.java
@@ -1,8 +1,10 @@
 package blade.addon.features.other;
 
+import blade.addon.utils.MathParser;
 import blade.addon.utils.config.values.ExtraOptions;
 import blade.addon.utils.data.ItemUtil;
 import blade.addon.utils.rendering.DrawEvents;
+import blade.addon.utils.rendering.RenderUtils;
 import net.minecraft.client.MinecraftClient;
 import net.minecraft.client.font.TextRenderer;
 import net.minecraft.client.gui.Click;
@@ -23,10 +25,11 @@ public class SearchBar {
     private static TextFieldWidget searchBar;
     private static boolean shouldDisplay = false;
     private static String searchTerm = "";
+    private static double parsedValue = Double.NaN;
 
     public static void init() {
         DrawEvents.INVENTORY_SLOT_AFTER.register((context, item, x, y) -> {
-            if (shouldDisplay() && !searchTerm.isEmpty() && ExtraOptions.toggleableSearchBar) {
+            if (shouldDisplay() && !searchTerm.isEmpty() && ExtraOptions.toggleableSearchBar && Double.isNaN(parsedValue)) {
                 if (!matches(item)) {
                     context.fill(x, y, x + 16, y + 16, 0xaa111111);
                 }
@@ -45,6 +48,16 @@ public class SearchBar {
     public static void render(DrawContext context, int mouseX, int mouseY, float deltaTicks) {
         if (!exists() || !shouldDisplay() || !ExtraOptions.toggleableSearchBar) return;
         searchBar.render(context, mouseX, mouseY, deltaTicks);
+
+        if (!Double.isNaN(parsedValue)) {
+            String expression = "  §e= §2"+ RenderUtils.formatNumber((float) parsedValue);
+            TextRenderer textRenderer = MinecraftClient.getInstance().textRenderer;
+            if (textRenderer == null) return;
+
+            int textX = searchBar.getX() + textRenderer.getWidth(searchTerm) + 4;
+            int textY = searchBar.getY() + (searchBar.getHeight() - 8) / 2;
+            context.drawText(textRenderer, expression, textX, textY, 0xffffffff, true);
+        }
     }
 
     public static boolean keyPressed(KeyInput input) {
@@ -87,7 +100,10 @@ public class SearchBar {
         if (window == null || textRenderer == null) return false;
 
         searchBar = new TextFieldWidget(textRenderer, (window.getScaledWidth() - SEARCH_WIDTH) / 2, SEARCH_Y, SEARCH_WIDTH, SEARCH_HEIGHT, Text.literal(""));
-        searchBar.setChangedListener(string -> searchTerm = string.toLowerCase());
+        searchBar.setChangedListener(string -> {
+            searchTerm = string.toLowerCase();
+            parsedValue = MathParser.parseExpression(searchTerm);
+        });
         return true;
     }
 
diff --git a/src/main/java/blade/addon/mixin/BossBarHudMixin.java b/src/main/java/blade/addon/mixin/BossBarHudMixin.java
index 1104327..7ad11d3 100644
--- a/src/main/java/blade/addon/mixin/BossBarHudMixin.java
+++ b/src/main/java/blade/addon/mixin/BossBarHudMixin.java
@@ -45,7 +45,7 @@ public class BossBarHudMixin {
             }
 
             float currHealth = health * bossBar.getPercent();
-            return Text.literal("§c" + name + " §a" + RenderUtils.formatHealth(currHealth) + "§7/§a" + RenderUtils.formatHealth(health));
+            return Text.literal("§c" + name + " §a" + RenderUtils.formatNumber(currHealth) + "§7/§a" + RenderUtils.formatNumber(health));
         } catch (Exception e) {
             Debug.LOGGER.error("Failed to modify bossbar name!", e);
             return bossBar.getName();
diff --git a/src/main/java/blade/addon/utils/Constants.java b/src/main/java/blade/addon/utils/Constants.java
index ce29150..f9cb463 100644
--- a/src/main/java/blade/addon/utils/Constants.java
+++ b/src/main/java/blade/addon/utils/Constants.java
@@ -9,13 +9,21 @@ public class Constants {
     public static final double TICK_DURATION = 0.05;
     public static final int TEXT_HEIGHT = 10;
 
+    public static final int BLACK = 0xff000000;
+    public static final int DARK_BLUE = 0xff0000aa;
+    public static final int DARK_GREEN = 0xff00aa00;
+    public static final int DARK_AQUA = 0xff00aaaa;
+    public static final int DARK_RED = 0xffaa0000;
+    public static final int DARK_PURPLE = 0xffaa00aa;
+    public static final int GOLD = 0xffffaa00;
+    public static final int GRAY = 0xffaaaaaa;
+    public static final int DARK_GRAY = 0xff555555;
+    public static final int BLUE = 0xff5555ff;
     public static final int GREEN = 0xff55ff55;
-    public static final int ORANGE = 0xffffaa00;
+    public static final int AQUA = 0xff55ffff;
     public static final int RED = 0xffff5555;
-    public static final int YELLOW = 0xffffff55;
-    public static final int GOLD = 0xffffaa00;
     public static final int LIGHT_PURPLE = 0xffff55ff;
-    public static final int DARK_PURPLE = 0xffaa00aa;
+    public static final int YELLOW = 0xffffff55;
 
     public static final int FAIL = 0;
     public static final int SUCCESS = 1;
diff --git a/src/main/java/blade/addon/utils/MathParser.java b/src/main/java/blade/addon/utils/MathParser.java
new file mode 100644
index 0000000..9683ad9
--- /dev/null
+++ b/src/main/java/blade/addon/utils/MathParser.java
@@ -0,0 +1,306 @@
+package blade.addon.utils;
+
+import java.util.ArrayList;
+import java.util.Stack;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+/**
+ * This is purely made for the searchbar.
+ * The parser is based on Shunting Yard and Reverse Polish Notation.
+ * Yes this is commented more than other files, but this was more fun,
+ * so I actually felt like documenting it.
+ */
+public class MathParser {
+
+    private static final Pattern NUMBER_PATTERN = Pattern.compile("^(?<num>-?\\d+)(?<decimal>\\.\\d+)?(?<unit>[bBmMkK])?");
+    private static final Pattern TOKEN_PATTERN = Pattern.compile("[()+\\-*/^]");
+
+    /**
+     * Parses an expression and returns the value.
+     * @param string The expression to parse
+     * @return The value if it's a valid expression, otherwise NaN
+     */
+    public static double parseExpression(String string) {
+        if (string == null) return Double.NaN;
+
+        ArrayList<String> tokens;
+        try {
+            tokens = toTokens(string);
+        } catch (IllegalArgumentException e) {
+            return Double.NaN;
+        }
+
+        if (tokens.isEmpty()) return Double.NaN;
+
+        if (tokens.size() == 1) {
+            try {
+                //incase it's just a number
+                return tokenToNum(tokens.getFirst());
+            } catch (NumberFormatException ignored) {
+                return Double.NaN;
+            }
+        }
+
+        ArrayList<String> rpnTokens = toRPN(tokens);
+
+        if (rpnTokens == null) {
+            return Double.NaN;
+        }
+        try {
+            return parseRPN(rpnTokens);
+        } catch (IllegalArgumentException e) {
+            return Double.NaN;
+        }
+    }
+
+    /**
+     * Converts a String to a number.
+     * Supports orders of magnitudes like b, m and k.
+     * It also supports negative and decimal numbers.
+     * If it can't convert the number,
+     * {@code NumberFormatException} will be thrown.
+     *
+     * @param token The token to convert to a number
+     * @return The converted number
+     */
+    private static double tokenToNum(String token) {
+        Matcher matcher = NUMBER_PATTERN.matcher(token);
+        if (!matcher.find()) {
+            throw new NumberFormatException();
+        }
+
+        String num = matcher.group("num");
+        String decimal = matcher.group("decimal");
+        String unit = matcher.group("unit");
+
+        double value;
+
+        if (decimal != null) {
+            value = Double.parseDouble(num + decimal);
+        } else {
+            value = Double.parseDouble(num);
+        }
+
+
+        if (unit != null) {
+            switch (unit) {
+                case "b", "B":
+                    value *= 1_000_000_000;
+                    break;
+                case "m", "M":
+                    value *= 1000_000;
+                    break;
+                case "k", "K":
+                    value *= 1000;
+                    break;
+            }
+        }
+
+        return value;
+    }
+
+
+    /**
+     * Parses an RPN token, Will throw an {@code IllegalArgumentException} when handling an invalid token.
+     * @param stack the stack to keep track of previously handled tokens
+     * @param token The token to parse
+     */
+    private static void handleTokenRPN(Stack<Double> stack, String token) {
+        try {
+            double value = tokenToNum(token);
+            stack.push(value);
+            return;
+        } catch (NumberFormatException ignored) {
+        }
+
+        if (stack.size() < 2) {
+            if (stack.size() == 1) {
+                if (token.equals("-")) {
+                    //edge case to make for example -(2+3) negative
+                    stack.push(stack.pop() * -1);
+                    return;
+                }
+            }
+
+            throw new IllegalArgumentException("Invalid token");
+        }
+
+
+        double right = stack.pop();
+        double left = stack.pop();
+
+        switch (token) {
+            case "+":
+                stack.push(left + right);
+                break;
+            case "-":
+                stack.push(left - right);
+                break;
+            case "*":
+                stack.push(left * right);
+                break;
+            case "/":
+                stack.push(left / right);
+                break;
+            case "^":
+                stack.push(Math.pow(left, right));
+                break;
+            default:
+                throw new IllegalArgumentException("Invalid operator");
+        }
+    }
+
+    /**
+     * Parses an expression of tokens in RPN.
+     * Reference: <a href="https://inspirnathan.com/posts/150-reverse-polish-notation-evaluator-in-javascript/">reverse-polish-notation-evaluator-in-javascript</a>
+     * @param tokens The tokens to parse
+     * @return The parsed value, {@code Double.NaN} if its invalid
+     */
+    private static double parseRPN(ArrayList<String> tokens) {
+        Stack<Double> stack = new Stack<>();
+
+        for (String token : tokens) {
+            handleTokenRPN(stack, token);
+        }
+
+        if (stack.isEmpty()) return Double.NaN;
+        return stack.pop();
+    }
+
+    private static int getPrecedence(String token) {
+        return switch (token) {
+            case "^" -> 4;
+            case "*", "/" -> 3;
+            case "+", "-" -> 2;
+            default -> -1;
+        };
+    }
+
+    private static String getAssociative(String token) {
+        return switch (token) {
+            case "^" -> "right";
+            case "*", "/", "+", "-" -> "left";
+            default -> "";
+        };
+    }
+
+    private static boolean isANumber(String token) {
+        Matcher matcher = NUMBER_PATTERN.matcher(token);
+        return matcher.find();
+    }
+
+    private static boolean addToken(ArrayList<String> output, Stack<String> stack, String token) {
+        if (isANumber(token)) {
+            output.add(token);
+            return true;
+        } else if (getPrecedence(token) != -1) {
+            if (stack.isEmpty()) {
+                stack.push(token);
+                return true;
+            }
+
+            String top = stack.peek();
+
+            while (!top.equals("(") && (getPrecedence(top) > getPrecedence(token)
+                    || (getPrecedence(top) == getPrecedence(token) && getAssociative(token).equals("left")))
+            ) {
+                output.add(stack.pop());
+                if (stack.isEmpty()) break;
+                top = stack.peek();
+            }
+            stack.push(token);
+            return true;
+
+        } else if (token.equals("(")) {
+            stack.push(token);
+            return true;
+        } else if (token.equals(")")) {
+            String top = stack.peek();
+            while (!top.equals("(")) {
+                if (stack.isEmpty()) return false;
+                output.add(stack.pop());
+                if (stack.isEmpty()) return false;
+                top = stack.peek();
+            }
+
+            if (stack.isEmpty() || !stack.peek().equals("(")) return false;
+            stack.pop();
+            return true;
+        } else {
+            return false;
+        }
+    }
+
+    /**
+     * Converts a list of tokens to a new in RPN
+     * Refrence: <a href="https://inspirnathan.com/posts/151-shunting-yard-algorithm-in-javascript/">151-shunting-yard-algorithm-in-javascript</a>
+     * @param tokens The tokens to convert
+     * @return The list of tokens in RPN
+     */
+    private static ArrayList<String> toRPN(ArrayList<String> tokens) {
+        ArrayList<String> output = new ArrayList<>();
+        Stack<String> stack = new Stack<>();
+
+        for (String token : tokens) {
+            if (!addToken(output, stack, token)) {
+                return null;
+            }
+        }
+
+        while (!stack.isEmpty()) {
+            String token = stack.pop();
+            if (token.equals("(")) return null;
+            output.add(token);
+        }
+
+        return output;
+    }
+
+    /**
+     * Split an expression string to an ArrayList of tokens.
+     * Throws an {@code IllegalArgumentException} when an invalid token is read.
+     *
+     * @param string The string to split into token
+     * @return The list of tokens
+     */
+    private static ArrayList<String> toTokens(String string) {
+        ArrayList<String> output = new ArrayList<>();
+        String copiedString = string.replaceAll(" ", "");
+
+        //to handle binary subtraction not getting confused
+        //with unary subtraction
+        boolean wasPrevNum = false;
+
+        while (!copiedString.isEmpty()) {
+            Matcher matcher = NUMBER_PATTERN.matcher(copiedString);
+            if (!wasPrevNum && matcher.find()) {
+                String token = matcher.group();
+                int index = copiedString.indexOf(token);
+                if (index == 0) {
+                    copiedString = copiedString.substring(token.length());
+                    output.add(token);
+                    wasPrevNum = true;
+                    continue;
+                }
+            }
+
+            matcher = TOKEN_PATTERN.matcher(copiedString);
+            if (matcher.find()) {
+                String token = matcher.group();
+                int index = copiedString.indexOf(token);
+                if (index == 0) {
+                    copiedString = copiedString.substring(token.length());
+                    output.add(token);
+                    wasPrevNum = false;
+                    continue;
+                }
+            }
+
+            throw new IllegalArgumentException("String: " + string + " has invalid token, ended at: " + copiedString);
+        }
+
+        return output;
+    }
+
+}
diff --git a/src/main/java/blade/addon/utils/config/Config.java b/src/main/java/blade/addon/utils/config/Config.java
index fb9c0d7..df75971 100644
--- a/src/main/java/blade/addon/utils/config/Config.java
+++ b/src/main/java/blade/addon/utils/config/Config.java
@@ -273,7 +273,7 @@ public class Config {
         highlight.add(new ConfigDouble(Text.literal("Extra Wither Width"), () -> MobHighlight.witherExtraWidth, num -> MobHighlight.witherExtraWidth = num, 0.1, 0, 1.5));
         highlight.add(new ConfigInt(Text.literal("Outline Width"), () -> MobHighlight.outlineWidth, num -> MobHighlight.outlineWidth = num, 1, 1, 10));
         highlight.add(new ConfigOptions<>(Text.literal("Highlight mode"), MobHighlight.HighlightType.values(), () -> MobHighlight.currentHighlight, type -> MobHighlight.currentHighlight = type));
-        highlight.add(new ConfigBool(Text.literal("Hide none stared nametags"), () -> MobHighlight.hideNoneStaredNameTags, bool -> MobHighlight.hideNoneStaredNameTags = bool));
+        highlight.add(new ConfigBool(Text.literal("Hide none starred nametags"), () -> MobHighlight.hideNoneStaredNameTags, bool -> MobHighlight.hideNoneStaredNameTags = bool));
 
         highlight.add(new ConfigColor(Text.literal("Star mob filled color"), () -> MobHighlight.starFilledColor, color -> MobHighlight.starFilledColor = color, "star-filled", true));
         highlight.add(new ConfigColor(Text.literal("Star mob outline color"), () -> MobHighlight.starOutlineColor, color -> MobHighlight.starOutlineColor = color, "star-outline", true));
diff --git a/src/main/java/blade/addon/utils/rendering/RenderUtils.java b/src/main/java/blade/addon/utils/rendering/RenderUtils.java
index 0e7382c..3c850b7 100644
--- a/src/main/java/blade/addon/utils/rendering/RenderUtils.java
+++ b/src/main/java/blade/addon/utils/rendering/RenderUtils.java
@@ -29,7 +29,7 @@ public class RenderUtils {
     }
 
     public static int getStatusColor(int minGreen, int minOrange, int value) {
-        return value >= minGreen ? Constants.GREEN : value >= minOrange ? Constants.ORANGE : Constants.RED;
+        return value >= minGreen ? Constants.GREEN : value >= minOrange ? Constants.GOLD : Constants.RED;
     }
 
     public static void drawText(DrawContext context, HUDComponent component, Text text, int color) {
@@ -117,19 +117,17 @@ public class RenderUtils {
         renderText(context, matrices, text, pos.x, pos.y, pos.z, scale);
     }
 
-
-    public static String formatHealth(float health) {
+    public static String formatNumber(float num) {
         if (Floor7.capitalizeHealthNumbers) {
-            if (health >= 1e9) return String.format("%.1fB", health / 1e9);
-            if (health >= 1e6) return String.format("%.1fM", health / 1e6);
-            if (health >= 1e3) return String.format("%.1fK", health / 1e3);
-            return health + "";
+            if (num >= 1e9) return String.format("%.1fB", num / 1e9);
+            if (num >= 1e6) return String.format("%.1fM", num / 1e6);
+            if (num >= 1e3) return String.format("%.1fK", num / 1e3);
+            return num + "";
         } else {
-            if (health >= 1e9) return String.format("%.1fb", health / 1e9);
-            if (health >= 1e6) return String.format("%.1fm", health / 1e6);
-            if (health >= 1e3) return String.format("%.1fk", health / 1e3);
-            return health + "";
+            if (num >= 1e9) return String.format("%.1fb", num / 1e9);
+            if (num >= 1e6) return String.format("%.1fm", num / 1e6);
+            if (num >= 1e3) return String.format("%.1fk", num / 1e3);
+            return num + "";
         }
     }
-
 }
- added a quiz progress toggle - added a toggle each to ignore color codes for chat filters and notifications - added current Section display - added changing colors with current section progress - added prev objective finished in section
- fixed invalid regexes not being caught in chat filters - hopefully made end of run splits not get sent in the middle of class xp
Gabriel Ellnebrand · 2026-02-17 · d968d9a
查看变更
src/main/java/blade/addon/Bladeaddons.java                               |  2 ++
 src/main/java/blade/addon/features/dungeon/QuizTimer.java                | 23 ++++++++++++++++++++++-
 src/main/java/blade/addon/features/dungeon/f7/terms/CurrentSection.java  | 33 +++++++++++++++++++++++++++++++++
 src/main/java/blade/addon/features/dungeon/f7/terms/SectionProgress.java | 65 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++--
 src/main/java/blade/addon/features/filter/Filters.java                   | 15 ++++++++++++++-
 src/main/java/blade/addon/features/notifications/Notifications.java      |  6 ++++++
 src/main/java/blade/addon/utils/config/Config.java                       | 24 ++++++++++++++++++++----
 src/main/java/blade/addon/utils/config/components/Components.java        |  6 +++++-
 src/main/java/blade/addon/utils/config/values/Dungeons.java              |  3 +++
 src/main/java/blade/addon/utils/config/values/ExtraOptions.java          |  6 ++++++
 src/main/java/blade/addon/utils/config/values/Floor7.java                |  9 +++++++++
 src/main/java/blade/addon/utils/data/TextUtil.java                       |  5 +++++
 src/main/java/blade/addon/utils/dungeon/Phase.java                       |  6 ++++--
 13 files changed, 192 insertions(+), 11 deletions(-)
diff --git a/src/main/java/blade/addon/Bladeaddons.java b/src/main/java/blade/addon/Bladeaddons.java
index 8fb5e3e..21eca8c 100644
--- a/src/main/java/blade/addon/Bladeaddons.java
+++ b/src/main/java/blade/addon/Bladeaddons.java
@@ -28,6 +28,7 @@ import blade.addon.features.dungeon.f7.maxor.MaxorTickTimer;
 import blade.addon.features.dungeon.f7.storm.DistanceToLedge;
 import blade.addon.features.dungeon.f7.storm.PillarExplode;
 import blade.addon.features.dungeon.f7.storm.StormTickTimer;
+import blade.addon.features.dungeon.f7.terms.CurrentSection;
 import blade.addon.features.dungeon.f7.terms.DeviceNotifier;
 import blade.addon.features.dungeon.f7.terms.GoldorTickTimer;
 import blade.addon.features.dungeon.f7.terms.LeapNotification;
@@ -158,5 +159,6 @@ public class Bladeaddons implements ModInitializer {
         SectionCompletion.init();
         MaxorTickTimer.init();
         BloodNotifier.init();
+        CurrentSection.init();
     }
 }
\ No newline at end of file
diff --git a/src/main/java/blade/addon/features/dungeon/QuizTimer.java b/src/main/java/blade/addon/features/dungeon/QuizTimer.java
index 40cf5d5..5f27038 100644
--- a/src/main/java/blade/addon/features/dungeon/QuizTimer.java
+++ b/src/main/java/blade/addon/features/dungeon/QuizTimer.java
@@ -1,11 +1,16 @@
 package blade.addon.features.dungeon;
 
+import blade.addon.utils.Constants;
 import blade.addon.utils.Location;
 import blade.addon.utils.config.values.Dungeons;
+import blade.addon.utils.config.values.ExtraOptions;
 import blade.addon.utils.events.Events;
 import blade.addon.utils.rendering.RenderUtils;
 import config.practical.hud.HUDComponent;
+import net.minecraft.client.MinecraftClient;
+import net.minecraft.client.font.TextRenderer;
 import net.minecraft.client.gui.DrawContext;
+import net.minecraft.text.Text;
 
 public class QuizTimer {
 
@@ -13,6 +18,7 @@ public class QuizTimer {
     private static final int QUESTION_DURATION = 100;
 
     private static int tick = 0;
+    private static int stage = 0;
 
     public static void init() {
 
@@ -22,10 +28,13 @@ public class QuizTimer {
             String string = text.getString();
             if (string.equals("[STATUE] Oruo the Omniscient: I am Oruo the Omniscient. I have lived many lives. I have learned all there is to know.")) {
                 tick = START_DURATION;
+                stage = 1;
             } else if (string.equals("[STATUE] Oruo the Omniscient: 2 questions left... Then you will have proven your worth to me!")) {
                 tick = QUESTION_DURATION;
+                stage = 2;
             } else if (string.equals("[STATUE] Oruo the Omniscient: One more question!")) {
                 tick = QUESTION_DURATION;
+                stage = 3;
             }
 
             return false;
@@ -38,6 +47,7 @@ public class QuizTimer {
 
         Events.ON_LOCATION_CHANGE.register(newLocation -> {
             tick = 0;
+            stage = 0;
             return false;
         });
     }
@@ -47,7 +57,18 @@ public class QuizTimer {
     }
 
     public static void render(HUDComponent component, DrawContext context) {
-       RenderUtils.drawPrefixedTimer(component, context, "Quiz", tick);
+        TextRenderer textRenderer = MinecraftClient.getInstance().textRenderer;
+        if (textRenderer == null) return;
+
+        if (Dungeons.quizProgress) {
+            Text drawnText = Text.literal("Quiz " + "(").withColor(ExtraOptions.timerPrefixColor)
+                    .append(Text.literal(stage + "/3").withColor(0xffffffff)
+                            .append(Text.literal("): ").withColor(ExtraOptions.timerPrefixColor))
+                            .append(Text.literal(Constants.DECIMAL_FORMAT.format(tick * Constants.TICK_DURATION) + "s").withColor(0xffffffff)));
+            context.drawText(textRenderer, drawnText, component.getScaledX(), component.getScaledY(), 0xffffffff, true);
+        } else {
+            RenderUtils.drawPrefixedTimer(component, context, "Quiz", tick);
+        }
     }
 
 
diff --git a/src/main/java/blade/addon/features/dungeon/f7/terms/CurrentSection.java b/src/main/java/blade/addon/features/dungeon/f7/terms/CurrentSection.java
new file mode 100644
index 0000000..65a4c5f
--- /dev/null
+++ b/src/main/java/blade/addon/features/dungeon/f7/terms/CurrentSection.java
@@ -0,0 +1,33 @@
+package blade.addon.features.dungeon.f7.terms;
+
+import blade.addon.utils.config.values.Floor7;
+import blade.addon.utils.dungeon.Phase;
+import blade.addon.utils.events.Events;
+import blade.addon.utils.rendering.RenderUtils;
+import config.practical.hud.HUDComponent;
+import net.minecraft.client.gui.DrawContext;
+
+public class CurrentSection {
+
+    private static int section = 1;
+
+    public static void init() {
+        Events.ON_SECTION_CHANGE.register(() -> {
+            section++;
+            return false;
+        });
+        Events.ON_LOCATION_CHANGE.register(newLocation -> {
+            section = 1;
+            return false;
+        });
+    }
+
+    public static boolean display() {
+        return Floor7.showCurrentSection && Phase.inTerminals();
+    }
+
+    public static void render(HUDComponent component, DrawContext context) {
+        RenderUtils.drawPrefixedText(component, context, "Section", " " + section);
+    }
+
+}
diff --git a/src/main/java/blade/addon/features/dungeon/f7/terms/SectionProgress.java b/src/main/java/blade/addon/features/dungeon/f7/terms/SectionProgress.java
index e48c195..fa1153e 100644
--- a/src/main/java/blade/addon/features/dungeon/f7/terms/SectionProgress.java
+++ b/src/main/java/blade/addon/features/dungeon/f7/terms/SectionProgress.java
@@ -1,6 +1,7 @@
 package blade.addon.features.dungeon.f7.terms;
 
 import blade.addon.utils.config.values.Floor7;
+import blade.addon.utils.data.TextUtil;
 import blade.addon.utils.dungeon.Phase;
 import blade.addon.utils.dungeon.Section;
 import blade.addon.utils.events.Events;
@@ -13,18 +14,48 @@ public class SectionProgress {
 
     private static int completed = 0;
     private static int sectionTotal = 7;
+    private static String prevObjective = "";
+
+    private static String completedFormat = "";
+    private static String objectiveFormat = "";
 
     public static void init() {
 
+        updateObjectiveFormat();
+        updateProgressFormat();
+
+        Events.ON_GAME_MESSAGE.register((message) -> {
+            if (!Phase.inTerminals() || !Floor7.sectionPrevObjective) return false;
+
+            String string = message.getString();
+            if (string.equals("The gate has been destroyed!")) {
+                prevObjective = "Gate Destroyed";
+                updateObjectiveFormat();
+            }
+
+            else if (string.equals("The gate will open in 5 seconds!")) {
+                prevObjective = "Break Gate";
+                updateObjectiveFormat();
+            }
+
+            return false;
+        });
+
         Events.ON_TERMINAL.register((formattedName, action, objective, current, total) -> {
             completed = current;
             sectionTotal = total;
+            prevObjective = TextUtil.capitaliseFirst(objective);
+            updateObjectiveFormat();
+            updateProgressFormat();
             return false;
         });
 
         Events.ON_SECTION_CHANGE.register(() -> {
             if (completed == sectionTotal) completed = 0;
             sectionTotal = getTotal();
+            prevObjective = "";
+            updateObjectiveFormat();
+            updateProgressFormat();
             return false;
         });
 
@@ -36,8 +67,33 @@ public class SectionProgress {
 
     }
 
+    private static void updateObjectiveFormat() {
+        objectiveFormat = switch (prevObjective) {
+            case "Lever", "Gate Destroyed" -> "§c";
+            case "Device" -> "§d";
+            case "Terminal" -> "§b";
+            case "Break Gate" -> "§5§l";
+            default -> "";
+        };
+    }
+
+    private static void updateProgressFormat() {
+        if (completed >= sectionTotal) completedFormat = "§6§l";
+        else if (sectionTotal - completed == 1 || (completed == 7 && sectionTotal == 8)) completedFormat = "§a";
+        else if (completed >= 3) completedFormat = "§e";
+        else completedFormat = "§c";
+    }
+
     private static int getTotal() {
-        return Section.getSection() != 2? 7: 8;
+        return Section.getSection() != 2 ? 7 : 8;
+    }
+
+    private static Text getProgressText() {
+        if (Floor7.sectionColorProgress) {
+            return Text.literal("§f(" + completedFormat + completed + "§f/§a" + sectionTotal + "§f)");
+        } else {
+            return Text.literal("§a(§c" + completed + "§a/" + sectionTotal + ")");
+        }
     }
 
     public static boolean display() {
@@ -45,7 +101,12 @@ public class SectionProgress {
     }
 
     public static void render(HUDComponent component, DrawContext context) {
-        RenderUtils.drawCenteredText(context, component, Text.literal("§a(§c" + completed + "§a/" + sectionTotal + ")"));
+        if (Floor7.sectionPrevObjective) {
+            RenderUtils.drawCenteredText(context, component, Text.literal(objectiveFormat + prevObjective + " ").append(getProgressText()));
+        } else {
+            RenderUtils.drawCenteredText(context, component, getProgressText());
+        }
+
     }
 
 }
diff --git a/src/main/java/blade/addon/features/filter/Filters.java b/src/main/java/blade/addon/features/filter/Filters.java
index 183466e..571b7d0 100644
--- a/src/main/java/blade/addon/features/filter/Filters.java
+++ b/src/main/java/blade/addon/features/filter/Filters.java
@@ -1,12 +1,16 @@
 package blade.addon.features.filter;
 
 import blade.addon.utils.config.FolderUtility;
+import blade.addon.utils.config.values.ExtraOptions;
+import blade.addon.utils.debug.Debug;
 import config.practical.manager.ConfigManager;
 import config.practical.manager.ConfigValue;
 import net.fabricmc.fabric.api.client.message.v1.ClientReceiveMessageEvents;
+import net.minecraft.text.Text;
 
 import java.util.ArrayList;
 import java.util.List;
+import java.util.regex.PatternSyntaxException;
 
 public class Filters {
 
@@ -21,8 +25,17 @@ public class Filters {
         ClientReceiveMessageEvents.ALLOW_GAME.register((message, overlay) -> {
             if (overlay) return true;
             String string = message.getString();
+
+            if (ExtraOptions.ignoreColorCodesFilter) {
+                string = string.replaceAll("§.", "");
+            }
+
             for (String filter : filters) {
-                if (string.matches(filter)) return false;
+                try {
+                    if (string.matches(filter)) return false;
+                } catch (PatternSyntaxException e) {
+                    Debug.sendDebugMessage(Text.literal("Invalid regex: " + filter));
+                }
             }
             return true;
         });
diff --git a/src/main/java/blade/addon/features/notifications/Notifications.java b/src/main/java/blade/addon/features/notifications/Notifications.java
index 2f65f39..490c346 100644
--- a/src/main/java/blade/addon/features/notifications/Notifications.java
+++ b/src/main/java/blade/addon/features/notifications/Notifications.java
@@ -2,6 +2,7 @@ package blade.addon.features.notifications;
 
 import blade.addon.utils.Misc;
 import blade.addon.utils.config.FolderUtility;
+import blade.addon.utils.config.values.ExtraOptions;
 import blade.addon.utils.debug.Debug;
 import blade.addon.utils.events.Events;
 import blade.addon.utils.rendering.RenderUtils;
@@ -40,6 +41,11 @@ public class Notifications {
         load();
         Events.ON_GAME_MESSAGE.register(text -> {
             String message = text.getString();
+
+            if (ExtraOptions.ignoreColorCodesNotification) {
+                message = message.replaceAll("§.", "");
+            }
+
             if (Debug.sendNotiDebug) {
                 Misc.addChatMessage(Text.literal(message.replaceAll("§", "&")));
             }
diff --git a/src/main/java/blade/addon/utils/config/Config.java b/src/main/java/blade/addon/utils/config/Config.java
index ffd077e..fb9c0d7 100644
--- a/src/main/java/blade/addon/utils/config/Config.java
+++ b/src/main/java/blade/addon/utils/config/Config.java
@@ -59,8 +59,17 @@ public class Config {
     private static ConfigCategory general() {
         ConfigCategory general = new ConfigCategory("General");
         general.add(new ConfigString(Text.literal("Message prefix"), () -> ExtraOptions.textPrefix, str -> ExtraOptions.textPrefix = str));
-        general.add(new ConfigButton(Text.literal("Edit Notifications"), () -> MinecraftClient.getInstance().setScreen(new NotificationList())));
-        general.add(new ConfigButton(Text.literal("Edit Chat filters"), () -> MinecraftClient.getInstance().setScreen(new FilterList())));
+
+        ConfigSection notifications = new ConfigSection(Text.literal("Chat Notifications"));
+        notifications.add(new ConfigButton(Text.literal("Edit Notifications"), () -> MinecraftClient.getInstance().setScreen(new NotificationList())));
+        notifications.add(new ConfigBool(Text.literal("Ignore color codes"), () -> ExtraOptions.ignoreColorCodesNotification, bool -> ExtraOptions.ignoreColorCodesNotification = bool));
+        general.add(notifications);
+
+
+        ConfigSection chatFilter = new ConfigSection(Text.literal("Chat filter"));
+        chatFilter.add(new ConfigButton(Text.literal("Edit"), () -> MinecraftClient.getInstance().setScreen(new FilterList())));
+        chatFilter.add(new ConfigBool(Text.literal("Ignore color codes"), () -> ExtraOptions.ignoreColorCodesFilter, bool -> ExtraOptions.ignoreColorCodesFilter = bool));
+        general.add(chatFilter);
         return general;
     }
 
@@ -132,6 +141,7 @@ public class Config {
         dungeons.add(new ConfigBool(Text.literal("Highlight teammates"), () -> Dungeons.highlightTeammates, bool -> Dungeons.highlightTeammates = bool));
         dungeons.add(new ConfigBool(Text.literal("Render class names"), () -> Dungeons.renderClassName, bool -> Dungeons.renderClassName = bool));
         dungeons.add(new ConfigBool(Text.literal("Time until quiz question"), () -> Dungeons.quizTimer, bool -> Dungeons.quizTimer = bool));
+        dungeons.add(new ConfigBool(Text.literal("append quiz progress"), () -> Dungeons.quizProgress, bool -> Dungeons.quizProgress = bool));
         dungeons.add(new ConfigBool(Text.literal("Draw boss health numbers"), () -> Dungeons.bossHealthNumbers, bool -> Dungeons.bossHealthNumbers = bool));
         dungeons.add(new ConfigBool(Text.literal("Blood notifier on first 4 spawned"), () -> Dungeons.alertBloodSpawns, bool -> Dungeons.alertBloodSpawns = bool));
         dungeons.add(new ConfigBool(Text.literal("Blood notifier on all classes"), () -> Dungeons.alertBloodForAllClasses, bool -> Dungeons.alertBloodForAllClasses = bool));
@@ -184,12 +194,18 @@ public class Config {
         goldor.add(new ConfigBool(Text.literal("SS completion notification"), () -> Floor7.notifySSCompletion, bool -> Floor7.notifySSCompletion = bool));
         goldor.add(new ConfigBool(Text.literal("Melody warning notification"), () -> Floor7.notifiyMelody, bool -> Floor7.notifiyMelody = bool));
         goldor.add(new ConfigBool(Text.literal("Send terminal time stamps"), () -> Floor7.terminalTimeStamps, bool -> Floor7.terminalTimeStamps = bool));
-        goldor.add(new ConfigBool(Text.literal("Display section progress"), () -> Floor7.showSectionProgress, bool -> Floor7.showSectionProgress = bool));
         goldor.add(new ConfigBool(Text.literal("Section completion notification"), () -> Floor7.sectionCompletionNotification, bool -> Floor7.sectionCompletionNotification = bool));
         goldor.add(new ConfigSound(Text.literal("Section complete sound"), Floor7.sectionChangeSound, 2, 2, true));
-
+        goldor.add(new ConfigBool(Text.literal("Current section display"), () -> Floor7.showCurrentSection, bool -> Floor7.showCurrentSection = bool));
         floor7.add(goldor);
 
+        ConfigSection sectionProgress = new ConfigSection(Text.literal("Section progress"));
+        sectionProgress.add(new ConfigBool(Text.literal("Display section progress"), () -> Floor7.showSectionProgress, bool -> Floor7.showSectionProgress = bool));
+        sectionProgress.add(new ConfigBool(Text.literal("Change color with progress"), () -> Floor7.sectionColorProgress, bool -> Floor7.sectionColorProgress = bool));
+        sectionProgress.add(new ConfigBool(Text.literal("Include prev objective"), () -> Floor7.sectionPrevObjective, bool -> Floor7.sectionPrevObjective = bool));
+        floor7.add(sectionProgress);
+
+
         ConfigSection titles = new ConfigSection(Text.literal("Terminal titles"));
         titles.add(new ConfigBool(Text.literal("Disable titles on pre4"), () -> Floor7.disableTitlesAtPre4, bool -> Floor7.disableTitlesAtPre4 = bool));
         titles.add(new ConfigBool(Text.literal("Disable titles on ss"), () -> Floor7.disableTitlesAtSS, bool -> Floor7.disableTitlesAtSS = bool));
diff --git a/src/main/java/blade/addon/utils/config/components/Components.java b/src/main/java/blade/addon/utils/config/components/Components.java
index 550ba93..d4cd64f 100644
--- a/src/main/java/blade/addon/utils/config/components/Components.java
+++ b/src/main/java/blade/addon/utils/config/components/Components.java
@@ -18,6 +18,7 @@ import blade.addon.features.dungeon.f7.invincibility.InvincibilityDuration;
 import blade.addon.features.dungeon.f7.invincibility.InvincibilityTimer;
 import blade.addon.features.dungeon.f7.location.LocationNotifier;
 import blade.addon.features.dungeon.f7.maxor.MaxorStun;
+import blade.addon.features.dungeon.f7.terms.CurrentSection;
 import blade.addon.features.dungeon.f7.terms.GoldorTickTimer;
 import blade.addon.features.dungeon.f7.terms.LeapNotification;
 import blade.addon.features.dungeon.f7.terms.MelodyWarning;
@@ -142,7 +143,7 @@ public class Components {
     public static HUDComponent sectionProgressDisplay = new HUDComponent(0, 0, 30, 10, 1, "Section progress", SectionProgress::display,  SectionProgress::render, () -> Floor7.showSectionProgress);
 
     @ConfigValue
-    public static HUDComponent quizTimerDisplay = new HUDComponent(0, 0, 60, 10, 1, "Quiz timer", QuizTimer::display,  QuizTimer::render, () -> Dungeons.quizTimer);
+    public static HUDComponent quizTimerDisplay = new HUDComponent(0, 0, 80, 10, 1, "Quiz timer", QuizTimer::display,  QuizTimer::render, () -> Dungeons.quizTimer);
 
     @ConfigValue
     public static HUDComponent maxorStunDisplay = new HUDComponent(0, 0, 70, 10, 1, "Maxor stun display", MaxorStun::display,  MaxorStun::render, () -> Floor7.maxorStunDuration);
@@ -161,4 +162,7 @@ public class Components {
 
     @ConfigValue
     public static HUDComponent bloodNotificationDisplay = new HUDComponent(0, 0, NOTIFICATION_WIDTH, 10, 1, "blood notification", BloodNotifier::display,  BloodNotifier::render, () -> Dungeons.alertBloodSpawns &&  !Dungeons.combineScreenNotifications);
+
+    @ConfigValue
+    public static HUDComponent currentSectionDisplay = new HUDComponent(0, 0, 50, 10, 1, "Current section", CurrentSection::display,  CurrentSection::render, () -> Floor7.showCurrentSection);
 }
diff --git a/src/main/java/blade/addon/utils/config/values/Dungeons.java b/src/main/java/blade/addon/utils/config/values/Dungeons.java
index 3a20359..1c88caa 100644
--- a/src/main/java/blade/addon/utils/config/values/Dungeons.java
+++ b/src/main/java/blade/addon/utils/config/values/Dungeons.java
@@ -145,4 +145,7 @@ public class Dungeons {
 
     @ConfigValue
     public static boolean alertBloodForAllClasses = false;
+
+    @ConfigValue
+    public static boolean quizProgress = false;
 }
diff --git a/src/main/java/blade/addon/utils/config/values/ExtraOptions.java b/src/main/java/blade/addon/utils/config/values/ExtraOptions.java
index 321b854..a66e4e3 100644
--- a/src/main/java/blade/addon/utils/config/values/ExtraOptions.java
+++ b/src/main/java/blade/addon/utils/config/values/ExtraOptions.java
@@ -129,4 +129,10 @@ public class ExtraOptions {
 
     @ConfigValue
     public static boolean oldTubaSound = false;
+
+    @ConfigValue
+    public static boolean ignoreColorCodesFilter = false;
+
+    @ConfigValue
+    public static boolean ignoreColorCodesNotification = false;
 }
diff --git a/src/main/java/blade/addon/utils/config/values/Floor7.java b/src/main/java/blade/addon/utils/config/values/Floor7.java
index f70051e..7cc8a28 100644
--- a/src/main/java/blade/addon/utils/config/values/Floor7.java
+++ b/src/main/java/blade/addon/utils/config/values/Floor7.java
@@ -165,4 +165,13 @@ public class Floor7 {
 
     @ConfigValue
     public static boolean enableMaxorTickTimer = false;
+
+    @ConfigValue
+    public static boolean showCurrentSection = false;
+
+    @ConfigValue
+    public static boolean sectionColorProgress = false;
+
+    @ConfigValue
+    public static boolean sectionPrevObjective = false;
 }
diff --git a/src/main/java/blade/addon/utils/data/TextUtil.java b/src/main/java/blade/addon/utils/data/TextUtil.java
index b519465..6b09d71 100644
--- a/src/main/java/blade/addon/utils/data/TextUtil.java
+++ b/src/main/java/blade/addon/utils/data/TextUtil.java
@@ -121,4 +121,9 @@ public class TextUtil {
        return Constants.DECIMAL_FORMAT.format(tick * Constants.TICK_DURATION);
     }
 
+    public static String capitaliseFirst(String message) {
+        String strippedMessage = message.strip();
+        if (strippedMessage.length() < 2) return message;
+        return strippedMessage.substring(0, 1).toUpperCase() + strippedMessage.substring(1).toLowerCase();
+    }
 }
diff --git a/src/main/java/blade/addon/utils/dungeon/Phase.java b/src/main/java/blade/addon/utils/dungeon/Phase.java
index 58d5f16..b845024 100644
--- a/src/main/java/blade/addon/utils/dungeon/Phase.java
+++ b/src/main/java/blade/addon/utils/dungeon/Phase.java
@@ -148,7 +148,11 @@ public class Phase {
     private static void endRun() {
         runOver = true;
         currentPhase = currentSplits.size();
+        Scheduler.scheduleTask(Phase::printSplits, 2);
+        Events.ON_RUN_END.invoke(RunEndEvent::onRunEnd);
+    }
 
+    private static void printSplits() {
         Misc.addChatMessage(Text.literal("§aSplits: "));
         for (Split split : currentSplits) {
             split.end();
@@ -159,8 +163,6 @@ public class Phase {
             Text timeLost = Text.literal("§aApproximately §e" + Constants.DECIMAL_FORMAT.format(time) + "s §alost to lag.");
             Misc.addChatMessage(timeLost);
         }
-
-        Events.ON_RUN_END.invoke(RunEndEvent::onRunEnd);
     }
 
     public static int getPhase() {
- fixed warp cooldown reseting when it shouldn't
- added maxor tick timer (for vortie spring boots or smth) - added blood room first 4 mob spawn notifier
Gabriel Ellnebrand · 2026-02-17 · f5783f7
查看变更
src/main/java/blade/addon/Bladeaddons.java                                         | 16 ++++++++++------
 src/main/java/blade/addon/features/dungeon/BloodNotifier.java                      | 60 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 src/main/java/blade/addon/features/dungeon/WarpCooldown.java                       |  2 ++
 src/main/java/blade/addon/features/dungeon/f7/maxor/MaxorTickTimer.java            | 35 +++++++++++++++++++++++++++++++++++
 src/main/java/blade/addon/features/dungeon/f7/terms/GoldorTickTimer.java           |  2 --
 src/main/java/blade/addon/utils/config/Config.java                                 |  5 ++++-
 src/main/java/blade/addon/utils/config/components/CombinedScreenNotifications.java |  5 +++++
 src/main/java/blade/addon/utils/config/components/Components.java                  |  8 ++++++++
 src/main/java/blade/addon/utils/config/values/Dungeons.java                        |  6 ++++++
 src/main/java/blade/addon/utils/config/values/Floor7.java                          |  3 +++
 10 files changed, 133 insertions(+), 9 deletions(-)
diff --git a/src/main/java/blade/addon/Bladeaddons.java b/src/main/java/blade/addon/Bladeaddons.java
index 515c000..8fb5e3e 100644
--- a/src/main/java/blade/addon/Bladeaddons.java
+++ b/src/main/java/blade/addon/Bladeaddons.java
@@ -1,6 +1,7 @@
 package blade.addon;
 
 import blade.addon.features.dungeon.AutoRequeue;
+import blade.addon.features.dungeon.BloodNotifier;
 import blade.addon.features.dungeon.ChestCounter;
 import blade.addon.features.dungeon.ExplosiveShot;
 import blade.addon.features.dungeon.HidePlayers;
@@ -12,20 +13,21 @@ import blade.addon.features.dungeon.RunStartValidator;
 import blade.addon.features.dungeon.SecretSpawnTimer;
 import blade.addon.features.dungeon.WarpCooldown;
 import blade.addon.features.dungeon.f7.BossWaypoints;
-import blade.addon.features.dungeon.f7.invincibility.MaskHighlight;
-import blade.addon.features.dungeon.f7.maxor.CrystalSpawn;
-import blade.addon.features.dungeon.f7.storm.DistanceToLedge;
-import blade.addon.features.dungeon.f7.dragons.DragSpawnTimer;
-import blade.addon.features.dungeon.f7.storm.PillarExplode;
 import blade.addon.features.dungeon.f7.PredevTimer;
 import blade.addon.features.dungeon.f7.RelicTimer;
-import blade.addon.features.dungeon.f7.storm.StormTickTimer;
+import blade.addon.features.dungeon.f7.dragons.DragSpawnTimer;
 import blade.addon.features.dungeon.f7.dragons.DragonHealth;
 import blade.addon.features.dungeon.f7.invincibility.InvincibilityDuration;
 import blade.addon.features.dungeon.f7.invincibility.InvincibilityTimer;
+import blade.addon.features.dungeon.f7.invincibility.MaskHighlight;
 import blade.addon.features.dungeon.f7.location.LocationNotifier;
 import blade.addon.features.dungeon.f7.location.PositionMessages;
+import blade.addon.features.dungeon.f7.maxor.CrystalSpawn;
 import blade.addon.features.dungeon.f7.maxor.MaxorStun;
+import blade.addon.features.dungeon.f7.maxor.MaxorTickTimer;
+import blade.addon.features.dungeon.f7.storm.DistanceToLedge;
+import blade.addon.features.dungeon.f7.storm.PillarExplode;
+import blade.addon.features.dungeon.f7.storm.StormTickTimer;
 import blade.addon.features.dungeon.f7.terms.DeviceNotifier;
 import blade.addon.features.dungeon.f7.terms.GoldorTickTimer;
 import blade.addon.features.dungeon.f7.terms.LeapNotification;
@@ -154,5 +156,7 @@ public class Bladeaddons implements ModInitializer {
         SearchBar.init();
         TubaSound.init();
         SectionCompletion.init();
+        MaxorTickTimer.init();
+        BloodNotifier.init();
     }
 }
\ No newline at end of file
diff --git a/src/main/java/blade/addon/features/dungeon/BloodNotifier.java b/src/main/java/blade/addon/features/dungeon/BloodNotifier.java
new file mode 100644
index 0000000..5654e39
--- /dev/null
+++ b/src/main/java/blade/addon/features/dungeon/BloodNotifier.java
@@ -0,0 +1,60 @@
+package blade.addon.features.dungeon;
+
+import blade.addon.utils.Constants;
+import blade.addon.utils.Location;
+import blade.addon.utils.Misc;
+import blade.addon.utils.config.values.Dungeons;
+import blade.addon.utils.dungeon.DungeonClass;
+import blade.addon.utils.events.Events;
+import blade.addon.utils.rendering.RenderUtils;
+import config.practical.hud.HUDComponent;
+import net.minecraft.client.gui.DrawContext;
+import net.minecraft.sound.SoundEvents;
+import net.minecraft.text.Text;
+
+public class BloodNotifier {
+
+    private static boolean detected = false;
+    private static long prevTime = 0;
+
+    public static void init() {
+
+        Events.ON_GAME_MESSAGE.register(message -> {
+            if (detected || !Location.inDungeon()) return false;
+
+
+            if (message.getString().equals("[BOSS] The Watcher: Let's see how you can handle this.")) {
+                detected = true;
+                prevTime = System.currentTimeMillis();
+
+                if (correctClass()) {
+                    Misc.sendSound(SoundEvents.ENTITY_EXPERIENCE_ORB_PICKUP, 2, 0);
+                }
+            }
+
+            return false;
+        });
+
+        Events.ON_LOCATION_CHANGE.register(newLocation -> {
+            reset();
+            return false;
+        });
+    }
+
+    private static void reset() {
+        detected = false;
+    }
+
+    private static boolean correctClass() {
+        return Dungeons.alertBloodForAllClasses || DungeonClass.isClass(DungeonClass.MAGE);
+    }
+
+    public static boolean display() {
+        return Dungeons.alertBloodSpawns && System.currentTimeMillis() - prevTime < 2000 && correctClass();
+    }
+
+    public static void render(HUDComponent component, DrawContext context) {
+        RenderUtils.drawCenteredText(context, component, Text.literal("First four mobs spawned!"), Constants.RED);
+    }
+
+}
diff --git a/src/main/java/blade/addon/features/dungeon/WarpCooldown.java b/src/main/java/blade/addon/features/dungeon/WarpCooldown.java
index 5f6829f..11dd40c 100644
--- a/src/main/java/blade/addon/features/dungeon/WarpCooldown.java
+++ b/src/main/java/blade/addon/features/dungeon/WarpCooldown.java
@@ -21,6 +21,8 @@ public class WarpCooldown {
         ClientReceiveMessageEvents.GAME.register((message, overlay) -> {
             if (!Dungeons.enableWarpCooldown) return;
 
+            if ((endTime - System.currentTimeMillis()) > 0) return;
+
             Matcher matcher = PATTERN.matcher(message.getString());
             if (matcher.find()) {
                 endTime = System.currentTimeMillis() + WARP_COOLDOWN_MS;
diff --git a/src/main/java/blade/addon/features/dungeon/f7/maxor/MaxorTickTimer.java b/src/main/java/blade/addon/features/dungeon/f7/maxor/MaxorTickTimer.java
new file mode 100644
index 0000000..eebf65b
--- /dev/null
+++ b/src/main/java/blade/addon/features/dungeon/f7/maxor/MaxorTickTimer.java
@@ -0,0 +1,35 @@
+package blade.addon.features.dungeon.f7.maxor;
+
+import blade.addon.utils.Location;
+import blade.addon.utils.config.values.Floor7;
+import blade.addon.utils.dungeon.Phase;
+import blade.addon.utils.events.Events;
+import blade.addon.utils.rendering.RenderUtils;
+import config.practical.hud.HUDComponent;
+import net.minecraft.client.gui.DrawContext;
+
+public class MaxorTickTimer {
+
+    private static int tick = 0;
+
+    public static void init() {
+        Events.ON_SERVER_TICK.register(() -> {
+            if (Location.inDungeon() && Phase.inP1()) tick++;
+            return false;
+        });
+
+        Events.ON_LOCATION_CHANGE.register(newLocation -> {
+            tick = 0;
+            return false;
+        });
+    }
+
+    public static boolean display() {
+        return Floor7.enableMaxorTickTimer && Location.inDungeon() && Phase.inP1();
+    }
+
+    public static void render(HUDComponent component, DrawContext context) {
+        RenderUtils.drawTimer(component, context, tick, 0xffffffff);
+    }
+
+}
diff --git a/src/main/java/blade/addon/features/dungeon/f7/terms/GoldorTickTimer.java b/src/main/java/blade/addon/features/dungeon/f7/terms/GoldorTickTimer.java
index 67217ca..8819139 100644
--- a/src/main/java/blade/addon/features/dungeon/f7/terms/GoldorTickTimer.java
+++ b/src/main/java/blade/addon/features/dungeon/f7/terms/GoldorTickTimer.java
@@ -37,8 +37,6 @@ public class GoldorTickTimer {
         if (Floor7.inDeathTicks && !Floor7.makeGoldorTickUp) mod = 3.0 - mod;
         if (Floor7.inDeathTicks) num = mod;
 
-        System.out.println(num);
-
         int color = (mod < 1 ? Constants.GREEN : mod < 2 ? Constants.ORANGE : Constants.RED);
 
         RenderUtils.drawTimer(component, context, num, color);
diff --git a/src/main/java/blade/addon/utils/config/Config.java b/src/main/java/blade/addon/utils/config/Config.java
index e93b86b..ffd077e 100644
--- a/src/main/java/blade/addon/utils/config/Config.java
+++ b/src/main/java/blade/addon/utils/config/Config.java
@@ -133,6 +133,8 @@ public class Config {
         dungeons.add(new ConfigBool(Text.literal("Render class names"), () -> Dungeons.renderClassName, bool -> Dungeons.renderClassName = bool));
         dungeons.add(new ConfigBool(Text.literal("Time until quiz question"), () -> Dungeons.quizTimer, bool -> Dungeons.quizTimer = bool));
         dungeons.add(new ConfigBool(Text.literal("Draw boss health numbers"), () -> Dungeons.bossHealthNumbers, bool -> Dungeons.bossHealthNumbers = bool));
+        dungeons.add(new ConfigBool(Text.literal("Blood notifier on first 4 spawned"), () -> Dungeons.alertBloodSpawns, bool -> Dungeons.alertBloodSpawns = bool));
+        dungeons.add(new ConfigBool(Text.literal("Blood notifier on all classes"), () -> Dungeons.alertBloodForAllClasses, bool -> Dungeons.alertBloodForAllClasses = bool));
         return dungeons;
     }
 
@@ -151,6 +153,7 @@ public class Config {
         floor7.add(waypoints);
 
         ConfigSection maxor = new ConfigSection(Text.literal("Maxor"));
+        maxor.add(new ConfigBool(Text.literal("Maxor tick timer"), () -> Floor7.enableMaxorTickTimer, bool -> Floor7.enableMaxorTickTimer = bool));
         maxor.add(new ConfigBool(Text.literal("Crystal Spawn Time"), () -> Floor7.enableCrystalSpawnTime, bool -> Floor7.enableCrystalSpawnTime = bool));
         maxor.add(new ConfigBool(Text.literal("Crystal place reminder"), () -> Floor7.crystalPlaceReminder, bool -> Floor7.crystalPlaceReminder = bool));
         maxor.add(new ConfigBool(Text.literal("Display reminder instantly"), () -> Floor7.instantlyDisplayCrystalReminder, bool -> Floor7.instantlyDisplayCrystalReminder = bool));
@@ -158,7 +161,7 @@ public class Config {
         floor7.add(maxor);
 
         ConfigSection storm = new ConfigSection(Text.literal("Storm"));
-        storm.add(new ConfigBool(Text.literal("Storm Tick timer"), () -> Floor7.enableStormTickTimer, bool -> Floor7.enableStormTickTimer = bool));
+        storm.add(new ConfigBool(Text.literal("Storm tick timer"), () -> Floor7.enableStormTickTimer, bool -> Floor7.enableStormTickTimer = bool));
         storm.add(new ConfigColor(Text.literal("Timer color"), () -> Floor7.stormTickTimerColor, color -> Floor7.stormTickTimerColor = color, "storm-tick-timer-color", false));
         storm.add(new ConfigBool(Text.literal("Tick down from 5"), () -> Floor7.tickDownStormTickTimer, bool -> Floor7.tickDownStormTickTimer = bool));
         storm.add(new ConfigBool(Text.literal("First Death time"), () -> Floor7.enableStormDeathTime, bool -> Floor7.enableStormDeathTime = bool));
diff --git a/src/main/java/blade/addon/utils/config/components/CombinedScreenNotifications.java b/src/main/java/blade/addon/utils/config/components/CombinedScreenNotifications.java
index 374be19..7083d47 100644
--- a/src/main/java/blade/addon/utils/config/components/CombinedScreenNotifications.java
+++ b/src/main/java/blade/addon/utils/config/components/CombinedScreenNotifications.java
@@ -1,5 +1,6 @@
 package blade.addon.utils.config.components;
 
+import blade.addon.features.dungeon.BloodNotifier;
 import blade.addon.features.dungeon.RunStartValidator;
 import blade.addon.features.dungeon.KeyNotifier;
 import blade.addon.features.dungeon.f7.maxor.CrystalSpawn;
@@ -41,6 +42,8 @@ public class CombinedScreenNotifications {
             return true;
         } else if (SectionCompletion.display()) {
             return true;
+        } else if (BloodNotifier.display()) {
+            return true;
         }
         return false;
     }
@@ -66,6 +69,8 @@ public class CombinedScreenNotifications {
             ArrowSwapper.renderNotification(component, context);
         }   else if (SectionCompletion.display()) {
             SectionCompletion.render(component, context);
+        }else if (BloodNotifier.display()) {
+            BloodNotifier.render(component, context);
         }else {
             RenderUtils.drawCenteredText(context, component, Text.literal("Some notification"));
         }
diff --git a/src/main/java/blade/addon/utils/config/components/Components.java b/src/main/java/blade/addon/utils/config/components/Components.java
index 158e205..550ba93 100644
--- a/src/main/java/blade/addon/utils/config/components/Components.java
+++ b/src/main/java/blade/addon/utils/config/components/Components.java
@@ -1,5 +1,6 @@
 package blade.addon.utils.config.components;
 
+import blade.addon.features.dungeon.BloodNotifier;
 import blade.addon.features.dungeon.ChestCounter;
 import blade.addon.features.dungeon.QuizTimer;
 import blade.addon.features.dungeon.RunStartValidator;
@@ -7,6 +8,7 @@ import blade.addon.features.dungeon.KeyNotifier;
 import blade.addon.features.dungeon.SecretSpawnTimer;
 import blade.addon.features.dungeon.WarpCooldown;
 import blade.addon.features.dungeon.f7.maxor.CrystalSpawn;
+import blade.addon.features.dungeon.f7.maxor.MaxorTickTimer;
 import blade.addon.features.dungeon.f7.storm.DistanceToLedge;
 import blade.addon.features.dungeon.f7.dragons.DragSpawnTimer;
 import blade.addon.features.dungeon.f7.storm.PillarExplode;
@@ -153,4 +155,10 @@ public class Components {
 
     @ConfigValue
     public static HUDComponent sectionCompletionDisplay = new HUDComponent(0, 0, NOTIFICATION_WIDTH, 10, 1, "Section completion", SectionCompletion::display,  SectionCompletion::render, () -> Floor7.sectionCompletionNotification &&  !Dungeons.combineScreenNotifications);
+
+    @ConfigValue
+    public static HUDComponent maxorTickTimer = new HUDComponent(0, 0, TICK_TIMER_WIDTH, 10, 1, "Maxor Tick Timer", MaxorTickTimer::display, MaxorTickTimer::render, () -> Floor7.enableMaxorTickTimer);
+
+    @ConfigValue
+    public static HUDComponent bloodNotificationDisplay = new HUDComponent(0, 0, NOTIFICATION_WIDTH, 10, 1, "blood notification", BloodNotifier::display,  BloodNotifier::render, () -> Dungeons.alertBloodSpawns &&  !Dungeons.combineScreenNotifications);
 }
diff --git a/src/main/java/blade/addon/utils/config/values/Dungeons.java b/src/main/java/blade/addon/utils/config/values/Dungeons.java
index 5a8e063..3a20359 100644
--- a/src/main/java/blade/addon/utils/config/values/Dungeons.java
+++ b/src/main/java/blade/addon/utils/config/values/Dungeons.java
@@ -139,4 +139,10 @@ public class Dungeons {
 
     @ConfigValue
     public static boolean removeMaskPart = true;
+
+    @ConfigValue
+    public static boolean alertBloodSpawns = false;
+
+    @ConfigValue
+    public static boolean alertBloodForAllClasses = false;
 }
diff --git a/src/main/java/blade/addon/utils/config/values/Floor7.java b/src/main/java/blade/addon/utils/config/values/Floor7.java
index 58509f7..f70051e 100644
--- a/src/main/java/blade/addon/utils/config/values/Floor7.java
+++ b/src/main/java/blade/addon/utils/config/values/Floor7.java
@@ -162,4 +162,7 @@ public class Floor7 {
 
     @ConfigValue
     public static SoundData sectionChangeSound = new SoundData(SoundEvents.BLOCK_NOTE_BLOCK_PLING.value(), 0, 1);
+
+    @ConfigValue
+    public static boolean enableMaxorTickTimer = false;
 }
- fixed revive title not being shown after dying on pre4 - possibly improved at location notifier (needs testing)Gabriel Ellnebrand · 2026-02-14 · b1f0548
查看变更
src/main/java/blade/addon/features/dungeon/f7/location/LocationNotifier.java | 13 +++++++++----
 src/main/java/blade/addon/features/dungeon/f7/location/PositionMessage.java  |  7 +++++--
 src/main/java/blade/addon/features/dungeon/f7/location/PositionMessages.java |  5 +++--
 src/main/java/blade/addon/features/dungeon/f7/terms/DeviceNotifier.java      | 14 ++++++++++++--
 src/main/java/blade/addon/mixin/InGameHudMixin.java                          |  4 ++--
 src/main/java/blade/addon/utils/events/CustomEvents.java                     |  3 ++-
 6 files changed, 33 insertions(+), 13 deletions(-)
diff --git a/src/main/java/blade/addon/features/dungeon/f7/location/LocationNotifier.java b/src/main/java/blade/addon/features/dungeon/f7/location/LocationNotifier.java
index c7da250..acc1f15 100644
--- a/src/main/java/blade/addon/features/dungeon/f7/location/LocationNotifier.java
+++ b/src/main/java/blade/addon/features/dungeon/f7/location/LocationNotifier.java
@@ -28,13 +28,12 @@ public class LocationNotifier {
     public static void init() {
         Events.ON_PARTY_MESSAGE.register((username, message) -> {
             if (!Floor7.displayLocationNotification || !Location.inDungeon()) return false;
-            Matcher matcher = PATTERN.matcher(message);
+            Matcher matcher = PATTERN.matcher(format(message));
             if (!matcher.find()) return false;
             String action = matcher.group(1);
-
-            if (Floor7.dontNotifiyForYourself && EntityUtil.isClientPlayer(username)) return false;
-            String location =message.substring(action.length()) .replaceAll("§.", "");
+            String location = message.substring(message.indexOf(action) + action.length()).replaceAll("§.", "");
             if (PositionMessages.hasBeenSent(location)) return false;
+            if (Floor7.dontNotifiyForYourself && EntityUtil.isClientPlayer(username)) return false;
             startNotification(username, " is " + action + location + "!");
 
             return false;
@@ -43,6 +42,12 @@ public class LocationNotifier {
         ClientTickEvents.END_CLIENT_TICK.register(client -> ticks = Math.max(ticks - 1, 0));
     }
 
+    private static String format(String message) {
+        String strippedMessage = message.strip();
+        if (strippedMessage.length() < 2) return message;
+        return strippedMessage.substring(0, 1).toUpperCase() + strippedMessage.substring(1).toLowerCase();
+    }
+
     public static void startNotification(String username, String message) {
         int color = Constants.GOLD;
         if (Dungeons.useClassColors) {
diff --git a/src/main/java/blade/addon/features/dungeon/f7/location/PositionMessage.java b/src/main/java/blade/addon/features/dungeon/f7/location/PositionMessage.java
index 0292df8..f9590b7 100644
--- a/src/main/java/blade/addon/features/dungeon/f7/location/PositionMessage.java
+++ b/src/main/java/blade/addon/features/dungeon/f7/location/PositionMessage.java
@@ -13,6 +13,7 @@ public class PositionMessage {
     private final String[] checks;
     private final Box box;
     private boolean sent;
+    private boolean personallySent;
     private final int[] sections;
 
     public PositionMessage(String message, String[] checks, Box box, int[] sections) {
@@ -20,6 +21,7 @@ public class PositionMessage {
         this.checks = checks;
         this.box = box;
         this.sent = true;
+        this.personallySent = true;
         this.sections = sections;
         PositionMessages.positionMessages.add(this);
     }
@@ -36,10 +38,10 @@ public class PositionMessage {
     }
 
     public void tick(ClientPlayerEntity player) {
-        if (sent || !inAValidSection() || !inRange(player.getEntityPos())) return;
-        sent = true;
+        if (sent || personallySent || !inAValidSection() || !inRange(player.getEntityPos())) return;
         if (Floor7.enablePositionalMessages) {
             Misc.executeCommand("pc " + message);
+            personallySent = true;
         }
     }
 
@@ -63,6 +65,7 @@ public class PositionMessage {
 
     public void setSent(boolean sent) {
         this.sent = sent;
+        this.personallySent = sent;
     }
 
     public boolean sent() {
diff --git a/src/main/java/blade/addon/features/dungeon/f7/location/PositionMessages.java b/src/main/java/blade/addon/features/dungeon/f7/location/PositionMessages.java
index 9ce9033..3702eed 100644
--- a/src/main/java/blade/addon/features/dungeon/f7/location/PositionMessages.java
+++ b/src/main/java/blade/addon/features/dungeon/f7/location/PositionMessages.java
@@ -34,7 +34,7 @@ public class PositionMessages {
 
     public static void init() {
         ClientTickEvents.END_CLIENT_TICK.register(PositionMessages::tick);
-        RenderingEvents.FILLED_BLOCK.register(PositionMessages::render);
+        RenderingEvents.OUTLINE_ENTITY.register(PositionMessages::render);
         Events.ON_LOCATION_CHANGE.register(newLocation -> {
             disableAll();
             return false;
@@ -81,8 +81,9 @@ public class PositionMessages {
     }
 
     public static boolean hasBeenSent(String message) {
+        String lowered = message.toLowerCase();
         for (PositionMessage positionMessage : positionMessages) {
-            if (positionMessage.hasBeenSent(message)) {
+            if (positionMessage.hasBeenSent(lowered)) {
                 return true;
             }
         }
diff --git a/src/main/java/blade/addon/features/dungeon/f7/terms/DeviceNotifier.java b/src/main/java/blade/addon/features/dungeon/f7/terms/DeviceNotifier.java
index 6443316..39f721b 100644
--- a/src/main/java/blade/addon/features/dungeon/f7/terms/DeviceNotifier.java
+++ b/src/main/java/blade/addon/features/dungeon/f7/terms/DeviceNotifier.java
@@ -56,8 +56,18 @@ public class DeviceNotifier {
         return (player.getX() >= 63 && player.getX() <= 64 && player.getY() == 127 && player.getZ() >= 35 && player.getZ() <= 36);
     }
 
-    public static boolean disableTitles() {
-        return ((Floor7.disableTitlesAtPre4 && at4thDev()) || (Floor7.disableTitlesAtSS && atSS())) && Phase.inTerminals();
+    public static boolean disableTitles(Text title) {
+        if (!(((Floor7.disableTitlesAtPre4 && at4thDev()) || (Floor7.disableTitlesAtSS && atSS())) && Phase.inTerminals()))
+            return false;
+
+        String string = title.getString();
+
+        if (string.equals("§eYou became a ghost!") || string.equals("§7Hopefully your teammates will be able to revive you!") || string.equals("§e§lBEING REVIVED")) {
+            return false;
+        }
+
+        return (!string.matches("§aYou will be revived in \\ds"));
+
     }
 
     public static boolean display() {
diff --git a/src/main/java/blade/addon/mixin/InGameHudMixin.java b/src/main/java/blade/addon/mixin/InGameHudMixin.java
index d7c09be..2b58cd0 100644
--- a/src/main/java/blade/addon/mixin/InGameHudMixin.java
+++ b/src/main/java/blade/addon/mixin/InGameHudMixin.java
@@ -62,13 +62,13 @@ public class InGameHudMixin implements GameHud {
     @Inject(method = "setTitle", at = @At("HEAD"), cancellable = true, order = 2000)
     public void setTitle(Text title, CallbackInfo ci) {
         if (TitleHider.shouldHideTitle(title)) ci.cancel();
-        else if (DeviceNotifier.disableTitles()) ci.cancel();
+        else if (DeviceNotifier.disableTitles(title)) ci.cancel();
     }
 
     @Inject(method = "setSubtitle", at = @At("HEAD"), cancellable = true, order = 2000)
     public void setSubtitle(Text subtitle, CallbackInfo ci) {
         if (TitleHider.shouldHideTitle(subtitle)) ci.cancel();
-        else if (DeviceNotifier.disableTitles()) ci.cancel();
+        else if (DeviceNotifier.disableTitles(subtitle)) ci.cancel();
     }
 
     @Inject(method = "renderHeldItemTooltip", at=@At("HEAD"))
diff --git a/src/main/java/blade/addon/utils/events/CustomEvents.java b/src/main/java/blade/addon/utils/events/CustomEvents.java
index b2015dd..db805e8 100644
--- a/src/main/java/blade/addon/utils/events/CustomEvents.java
+++ b/src/main/java/blade/addon/utils/events/CustomEvents.java
@@ -18,6 +18,7 @@ public class CustomEvents {
         //party event
         ClientReceiveMessageEvents.GAME.register((message, overlay) -> {
             String string = message.getString();
+
             Matcher matcher = PARTY_PATTERN.matcher(string);
             if (!matcher.find()) return;
 
@@ -29,7 +30,7 @@ public class CustomEvents {
 
             String tempUsername = string.substring(PARTY_MSG_OFFSET, index).replaceAll("§.", "");
             if (index + 2 >= string.length()) return;
-            String sentMessage = string.substring(index + 2);
+            String sentMessage = string.substring(index + 2).strip();
 
             index = tempUsername.indexOf("]") + 2;
             if (index > -1 && index < tempUsername.length()) {
- made searchbar also search the lore of an item
- fixed hide terminal titles hiding titles like rev title - added dev command to test strings
Gabriel Ellnebrand · 2026-02-14 · 433d369
查看变更
src/main/java/blade/addon/features/dungeon/f7/terms/TitleHider.java | 23 ++++++-----------------
 src/main/java/blade/addon/features/other/SearchBar.java             | 14 +++++++++++---
 src/main/java/blade/addon/mixin/ClientPlayNetworkHandlerMixin.java  |  8 --------
 src/main/java/blade/addon/mixin/InGameHudMixin.java                 |  6 +++---
 src/main/java/blade/addon/utils/data/ItemUtil.java                  | 17 +++++++++++++++++
 src/main/java/blade/addon/utils/debug/Debug.java                    |  7 +++++++
 src/main/java/blade/addon/utils/dungeon/Section.java                | 11 +----------
 src/main/java/blade/addon/utils/interfaces/GameHud.java             |  2 +-
 8 files changed, 46 insertions(+), 42 deletions(-)
diff --git a/src/main/java/blade/addon/features/dungeon/f7/terms/TitleHider.java b/src/main/java/blade/addon/features/dungeon/f7/terms/TitleHider.java
index e0f13c5..a862130 100644
--- a/src/main/java/blade/addon/features/dungeon/f7/terms/TitleHider.java
+++ b/src/main/java/blade/addon/features/dungeon/f7/terms/TitleHider.java
@@ -1,12 +1,9 @@
 package blade.addon.features.dungeon.f7.terms;
 
 import blade.addon.utils.Location;
-import blade.addon.utils.Misc;
 import blade.addon.utils.config.values.Floor7;
 import blade.addon.utils.data.EntityUtil;
 import blade.addon.utils.dungeon.Phase;
-import blade.addon.utils.dungeon.Section;
-import net.minecraft.network.packet.s2c.play.SubtitleS2CPacket;
 import net.minecraft.text.Text;
 
 import java.util.regex.Matcher;
@@ -16,26 +13,18 @@ public class TitleHider {
 
     private static final Pattern TERMINALS_DONE_PATTERN = Pattern.compile("^(\\w+) (activated|completed) a (terminal|device|lever)! \\((\\d)/(\\d)\\)$");
 
-    public static void processSubtitle(SubtitleS2CPacket packet) {
-        if (!shouldHideTitle()) return;
-        if (Section.inSection(1) && DeviceNotifier.at4thDev()) return;
-        String string = packet.text().getString().replaceAll("§.", "");
+    public static boolean shouldHideTitle(Text title) {
+        if (!(Floor7.hideTerminalTitles && Phase.inP3() && Location.inDungeon())) return false;
+        String titleString = title.getString().replaceAll("§.", "");
 
-
-        Matcher matcher = TERMINALS_DONE_PATTERN.matcher(string);
+        Matcher matcher = TERMINALS_DONE_PATTERN.matcher(titleString);
         if (matcher.find()) {
             String name = matcher.group(1);
-            if (EntityUtil.isClientPlayer(name)) {
-                Misc.forceTitle(Text.empty(), packet.text());
-            }
-            //end of section msg is handled by the Section class currently
+            return !EntityUtil.isClientPlayer(name);
         } else {
-            Misc.forceTitle(Text.empty(), packet.text());
+            return false;
         }
-    }
 
-    public static boolean shouldHideTitle() {
-        return Floor7.hideTerminalTitles && Phase.inP3() && Location.inDungeon();
     }
 
 }
diff --git a/src/main/java/blade/addon/features/other/SearchBar.java b/src/main/java/blade/addon/features/other/SearchBar.java
index 4f72664..1ce04d8 100644
--- a/src/main/java/blade/addon/features/other/SearchBar.java
+++ b/src/main/java/blade/addon/features/other/SearchBar.java
@@ -1,6 +1,7 @@
 package blade.addon.features.other;
 
 import blade.addon.utils.config.values.ExtraOptions;
+import blade.addon.utils.data.ItemUtil;
 import blade.addon.utils.rendering.DrawEvents;
 import net.minecraft.client.MinecraftClient;
 import net.minecraft.client.font.TextRenderer;
@@ -10,6 +11,7 @@ import net.minecraft.client.gui.widget.TextFieldWidget;
 import net.minecraft.client.input.CharInput;
 import net.minecraft.client.input.KeyInput;
 import net.minecraft.client.util.Window;
+import net.minecraft.item.ItemStack;
 import net.minecraft.text.Text;
 import org.lwjgl.glfw.GLFW;
 
@@ -25,8 +27,7 @@ public class SearchBar {
     public static void init() {
         DrawEvents.INVENTORY_SLOT_AFTER.register((context, item, x, y) -> {
             if (shouldDisplay() && !searchTerm.isEmpty() && ExtraOptions.toggleableSearchBar) {
-                String name = item.getName().getString().toLowerCase();
-                if (!name.contains(searchTerm.toLowerCase()) || name.equals("air")) {
+                if (!matches(item)) {
                     context.fill(x, y, x + 16, y + 16, 0xaa111111);
                 }
 
@@ -34,6 +35,13 @@ public class SearchBar {
         });
     }
 
+    private static boolean matches(ItemStack item) {
+        String name = item.getName().getString().toLowerCase();
+        if (name.equals("air")) return false;
+
+        return (name.contains(searchTerm) || ItemUtil.containsIgnoreCaseLore(item, searchTerm));
+    }
+
     public static void render(DrawContext context, int mouseX, int mouseY, float deltaTicks) {
         if (!exists() || !shouldDisplay() || !ExtraOptions.toggleableSearchBar) return;
         searchBar.render(context, mouseX, mouseY, deltaTicks);
@@ -79,7 +87,7 @@ public class SearchBar {
         if (window == null || textRenderer == null) return false;
 
         searchBar = new TextFieldWidget(textRenderer, (window.getScaledWidth() - SEARCH_WIDTH) / 2, SEARCH_Y, SEARCH_WIDTH, SEARCH_HEIGHT, Text.literal(""));
-        searchBar.setChangedListener(string -> searchTerm = string);
+        searchBar.setChangedListener(string -> searchTerm = string.toLowerCase());
         return true;
     }
 
diff --git a/src/main/java/blade/addon/mixin/ClientPlayNetworkHandlerMixin.java b/src/main/java/blade/addon/mixin/ClientPlayNetworkHandlerMixin.java
index 7009d8b..da95d9a 100644
--- a/src/main/java/blade/addon/mixin/ClientPlayNetworkHandlerMixin.java
+++ b/src/main/java/blade/addon/mixin/ClientPlayNetworkHandlerMixin.java
@@ -1,6 +1,5 @@
 package blade.addon.mixin;
 
-import blade.addon.features.dungeon.f7.terms.TitleHider;
 import blade.addon.utils.Misc;
 import blade.addon.utils.config.values.ExtraOptions;
 import blade.addon.utils.debug.Debug;
@@ -22,7 +21,6 @@ import net.minecraft.network.packet.s2c.play.ParticleS2CPacket;
 import net.minecraft.network.packet.s2c.play.PlaySoundFromEntityS2CPacket;
 import net.minecraft.network.packet.s2c.play.PlaySoundS2CPacket;
 import net.minecraft.network.packet.s2c.play.PlayerListS2CPacket;
-import net.minecraft.network.packet.s2c.play.SubtitleS2CPacket;
 import net.minecraft.network.packet.s2c.play.TeamS2CPacket;
 import net.minecraft.scoreboard.Team;
 import net.minecraft.sound.SoundEvent;
@@ -99,12 +97,6 @@ public class ClientPlayNetworkHandlerMixin {
         }
     }
 
-    @Inject(method = "onSubtitle", at = @At("HEAD"))
-    private void onTitle(SubtitleS2CPacket packet, CallbackInfo ci) {
-        TitleHider.processSubtitle(packet);
-    }
-
-
     @Inject(method = "onEntitySpawn", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/network/ClientPlayNetworkHandler;playSpawnSound(Lnet/minecraft/entity/Entity;)V"))
     private void onEntitySpawn(EntitySpawnS2CPacket packet, CallbackInfo ci, @Local Entity entity) {
         Events.ON_ENTITY_SPAWNED.invoke(entityTrackEvent -> entityTrackEvent.onEntity(entity, world));
diff --git a/src/main/java/blade/addon/mixin/InGameHudMixin.java b/src/main/java/blade/addon/mixin/InGameHudMixin.java
index 53d1094..d7c09be 100644
--- a/src/main/java/blade/addon/mixin/InGameHudMixin.java
+++ b/src/main/java/blade/addon/mixin/InGameHudMixin.java
@@ -60,14 +60,14 @@ public class InGameHudMixin implements GameHud {
     }
 
     @Inject(method = "setTitle", at = @At("HEAD"), cancellable = true, order = 2000)
-    public void renderStatusEffectsOverLay(Text title, CallbackInfo ci) {
-        if (TitleHider.shouldHideTitle()) ci.cancel();
+    public void setTitle(Text title, CallbackInfo ci) {
+        if (TitleHider.shouldHideTitle(title)) ci.cancel();
         else if (DeviceNotifier.disableTitles()) ci.cancel();
     }
 
     @Inject(method = "setSubtitle", at = @At("HEAD"), cancellable = true, order = 2000)
     public void setSubtitle(Text subtitle, CallbackInfo ci) {
-        if (TitleHider.shouldHideTitle()) ci.cancel();
+        if (TitleHider.shouldHideTitle(subtitle)) ci.cancel();
         else if (DeviceNotifier.disableTitles()) ci.cancel();
     }
 
diff --git a/src/main/java/blade/addon/utils/data/ItemUtil.java b/src/main/java/blade/addon/utils/data/ItemUtil.java
index 6a58553..e1196ca 100644
--- a/src/main/java/blade/addon/utils/data/ItemUtil.java
+++ b/src/main/java/blade/addon/utils/data/ItemUtil.java
@@ -60,6 +60,23 @@ public class ItemUtil {
         return false;
     }
 
+    public static boolean containsIgnoreCaseLore(ItemStack item, String contain) {
+        if (item ==null) return false;
+        LoreComponent lore = item.get(DataComponentTypes.LORE);
+        if (lore == null) return false;
+
+        List<Text> lines = lore.lines();
+        if (lines.isEmpty()) return false;
+
+        for (Text line : lines.reversed()) {
+            String string = line.getString();
+            if (string.toLowerCase().contains(contain.toLowerCase())) {
+                return true;
+            }
+        }
+        return false;
+    }
+
     public static boolean containsNBT(ItemStack item, String contain) {
         NbtComponent nbt = item.get(DataComponentTypes.CUSTOM_DATA);
         if (nbt == null) {
diff --git a/src/main/java/blade/addon/utils/debug/Debug.java b/src/main/java/blade/addon/utils/debug/Debug.java
index 8031eab..c8af16a 100644
--- a/src/main/java/blade/addon/utils/debug/Debug.java
+++ b/src/main/java/blade/addon/utils/debug/Debug.java
@@ -8,6 +8,7 @@ import blade.addon.utils.Misc;
 import blade.addon.utils.dungeon.DungeonClass;
 import blade.addon.utils.dungeon.Phase;
 import blade.addon.utils.dungeon.Section;
+import blade.addon.utils.events.Events;
 import com.mojang.brigadier.CommandDispatcher;
 import com.mojang.brigadier.arguments.StringArgumentType;
 import net.fabricmc.fabric.api.client.command.v2.ClientCommandManager;
@@ -71,6 +72,12 @@ public class Debug {
                     return Constants.SUCCESS;
                 }))
 
+                .then(ClientCommandManager.literal("testString").then(ClientCommandManager.argument("message", StringArgumentType.string()).executes(context -> {
+                    String message = StringArgumentType.getString(context, "message");
+                    Events.ON_GAME_MESSAGE.invoke(gameMessageEvent -> gameMessageEvent.onGameMessage(Text.literal(message)));
+                    return Constants.SUCCESS;
+                })))
+
                 .then(ClientCommandManager.literal("relic")
                         .then(ClientCommandManager.literal("set")
                                 .then(ClientCommandManager.argument("name", StringArgumentType.string())
diff --git a/src/main/java/blade/addon/utils/dungeon/Section.java b/src/main/java/blade/addon/utils/dungeon/Section.java
index 520032d..44153f3 100644
--- a/src/main/java/blade/addon/utils/dungeon/Section.java
+++ b/src/main/java/blade/addon/utils/dungeon/Section.java
@@ -1,11 +1,10 @@
 package blade.addon.utils.dungeon;
 
-import blade.addon.features.dungeon.f7.terms.TitleHider;
 import blade.addon.utils.Constants;
-import blade.addon.utils.debug.Debug;
 import blade.addon.utils.Location;
 import blade.addon.utils.Misc;
 import blade.addon.utils.config.values.Floor7;
+import blade.addon.utils.debug.Debug;
 import blade.addon.utils.events.Events;
 import blade.addon.utils.events.interfaces.SectionEvent;
 import config.practical.hud.HUDComponent;
@@ -14,7 +13,6 @@ import net.minecraft.client.MinecraftClient;
 import net.minecraft.client.font.TextRenderer;
 import net.minecraft.client.gui.DrawContext;
 import net.minecraft.text.Text;
-import net.minecraft.util.Formatting;
 
 import java.util.List;
 import java.util.regex.Matcher;
@@ -75,9 +73,6 @@ public class Section {
                 currentSection = 1;
                 splits[0].start();
             } else if (Phase.inGoldorTunnel()) {
-                if (TitleHider.shouldHideTitle()) {
-                    Misc.forceTitle(Text.empty(), Text.literal("The Core entrance is opening!").formatted(Formatting.GREEN));
-                }
                 if (Debug.termInfo) {
                     Misc.addChatMessage(Text.literal("Terminals ended"));
                 }
@@ -193,10 +188,6 @@ public class Section {
             if (string.equals("The gate has been destroyed!")) {
                 gateBlownUp = true;
 
-                if (TitleHider.shouldHideTitle()) {
-                    Misc.forceTitle(Text.empty(), message);
-                }
-
                 if (completed == total) {
                     incrementSection();
                 }
diff --git a/src/main/java/blade/addon/utils/interfaces/GameHud.java b/src/main/java/blade/addon/utils/interfaces/GameHud.java
index ebbcfb0..9f67107 100644
--- a/src/main/java/blade/addon/utils/interfaces/GameHud.java
+++ b/src/main/java/blade/addon/utils/interfaces/GameHud.java
@@ -2,6 +2,6 @@ package blade.addon.utils.interfaces;
 
 import net.minecraft.text.Text;
 
-public interface    GameHud {
+public interface GameHud {
     void blade_addons$forceTitle(Text title, Text subtitle);
 }
- added section completion screen notification and soundGabriel Ellnebrand · 2026-02-14 · ac081e6
查看变更
src/main/java/blade/addon/Bladeaddons.java                                         |  2 ++
 src/main/java/blade/addon/features/dungeon/f7/terms/SectionCompletion.java         | 32 ++++++++++++++++++++++++++++++++
 src/main/java/blade/addon/utils/config/Config.java                                 |  3 +++
 src/main/java/blade/addon/utils/config/components/CombinedScreenNotifications.java |  5 +++++
 src/main/java/blade/addon/utils/config/components/Components.java                  |  4 ++++
 src/main/java/blade/addon/utils/config/values/Floor7.java                          |  6 ++++++
 6 files changed, 52 insertions(+)
diff --git a/src/main/java/blade/addon/Bladeaddons.java b/src/main/java/blade/addon/Bladeaddons.java
index a99289d..515c000 100644
--- a/src/main/java/blade/addon/Bladeaddons.java
+++ b/src/main/java/blade/addon/Bladeaddons.java
@@ -30,6 +30,7 @@ import blade.addon.features.dungeon.f7.terms.DeviceNotifier;
 import blade.addon.features.dungeon.f7.terms.GoldorTickTimer;
 import blade.addon.features.dungeon.f7.terms.LeapNotification;
 import blade.addon.features.dungeon.f7.terms.MelodyWarning;
+import blade.addon.features.dungeon.f7.terms.SectionCompletion;
 import blade.addon.features.dungeon.f7.terms.SectionProgress;
 import blade.addon.features.dungeon.f7.terms.TermStartTimer;
 import blade.addon.features.filter.Filters;
@@ -152,5 +153,6 @@ public class Bladeaddons implements ModInitializer {
         SelectedPetHighlight.init();
         SearchBar.init();
         TubaSound.init();
+        SectionCompletion.init();
     }
 }
\ No newline at end of file
diff --git a/src/main/java/blade/addon/features/dungeon/f7/terms/SectionCompletion.java b/src/main/java/blade/addon/features/dungeon/f7/terms/SectionCompletion.java
new file mode 100644
index 0000000..8cfa58f
--- /dev/null
+++ b/src/main/java/blade/addon/features/dungeon/f7/terms/SectionCompletion.java
@@ -0,0 +1,32 @@
+package blade.addon.features.dungeon.f7.terms;
+
+import blade.addon.utils.Misc;
+import blade.addon.utils.config.values.Floor7;
+import blade.addon.utils.events.Events;
+import blade.addon.utils.rendering.RenderUtils;
+import config.practical.hud.HUDComponent;
+import net.minecraft.client.gui.DrawContext;
+import net.minecraft.text.Text;
+import net.minecraft.util.Formatting;
+
+public class SectionCompletion {
+
+    private static long prevTime = 0;
+
+    public static void init() {
+        Events.ON_SECTION_CHANGE.register(() -> {
+            Misc.sendSound(Floor7.sectionChangeSound);
+            prevTime = System.currentTimeMillis();
+            return false;
+        });
+    }
+
+    public static boolean display() {
+        return System.currentTimeMillis() - prevTime < 1000 && Floor7.sectionCompletionNotification;
+    }
+
+    public static void render(HUDComponent component, DrawContext context) {
+        RenderUtils.drawCenteredText(context, component, Text.literal("Section completed!").formatted(Formatting.GREEN));
+    }
+
+}
diff --git a/src/main/java/blade/addon/utils/config/Config.java b/src/main/java/blade/addon/utils/config/Config.java
index 2922cde..e93b86b 100644
--- a/src/main/java/blade/addon/utils/config/Config.java
+++ b/src/main/java/blade/addon/utils/config/Config.java
@@ -182,6 +182,9 @@ public class Config {
         goldor.add(new ConfigBool(Text.literal("Melody warning notification"), () -> Floor7.notifiyMelody, bool -> Floor7.notifiyMelody = bool));
         goldor.add(new ConfigBool(Text.literal("Send terminal time stamps"), () -> Floor7.terminalTimeStamps, bool -> Floor7.terminalTimeStamps = bool));
         goldor.add(new ConfigBool(Text.literal("Display section progress"), () -> Floor7.showSectionProgress, bool -> Floor7.showSectionProgress = bool));
+        goldor.add(new ConfigBool(Text.literal("Section completion notification"), () -> Floor7.sectionCompletionNotification, bool -> Floor7.sectionCompletionNotification = bool));
+        goldor.add(new ConfigSound(Text.literal("Section complete sound"), Floor7.sectionChangeSound, 2, 2, true));
+
         floor7.add(goldor);
 
         ConfigSection titles = new ConfigSection(Text.literal("Terminal titles"));
diff --git a/src/main/java/blade/addon/utils/config/components/CombinedScreenNotifications.java b/src/main/java/blade/addon/utils/config/components/CombinedScreenNotifications.java
index d13de01..374be19 100644
--- a/src/main/java/blade/addon/utils/config/components/CombinedScreenNotifications.java
+++ b/src/main/java/blade/addon/utils/config/components/CombinedScreenNotifications.java
@@ -6,6 +6,7 @@ import blade.addon.features.dungeon.f7.maxor.CrystalSpawn;
 import blade.addon.features.dungeon.f7.terms.MelodyWarning;
 import blade.addon.features.dungeon.f7.storm.PillarExplode;
 import blade.addon.features.dungeon.f7.terms.DeviceNotifier;
+import blade.addon.features.dungeon.f7.terms.SectionCompletion;
 import blade.addon.features.notifications.Notifications;
 import blade.addon.features.other.ArrowSwapper;
 import blade.addon.features.other.SelectedPet;
@@ -38,6 +39,8 @@ public class CombinedScreenNotifications {
             return true;
         } else if (ArrowSwapper.displayNotification()) {
             return true;
+        } else if (SectionCompletion.display()) {
+            return true;
         }
         return false;
     }
@@ -61,6 +64,8 @@ public class CombinedScreenNotifications {
             SelectedPet.renderNotification(component, context);
         }   else if (ArrowSwapper.displayNotification()) {
             ArrowSwapper.renderNotification(component, context);
+        }   else if (SectionCompletion.display()) {
+            SectionCompletion.render(component, context);
         }else {
             RenderUtils.drawCenteredText(context, component, Text.literal("Some notification"));
         }
diff --git a/src/main/java/blade/addon/utils/config/components/Components.java b/src/main/java/blade/addon/utils/config/components/Components.java
index 0d6c1db..158e205 100644
--- a/src/main/java/blade/addon/utils/config/components/Components.java
+++ b/src/main/java/blade/addon/utils/config/components/Components.java
@@ -20,6 +20,7 @@ import blade.addon.features.dungeon.f7.terms.GoldorTickTimer;
 import blade.addon.features.dungeon.f7.terms.LeapNotification;
 import blade.addon.features.dungeon.f7.terms.MelodyWarning;
 import blade.addon.features.dungeon.f7.terms.DeviceNotifier;
+import blade.addon.features.dungeon.f7.terms.SectionCompletion;
 import blade.addon.features.dungeon.f7.terms.SectionProgress;
 import blade.addon.features.dungeon.f7.terms.TermStartTimer;
 import blade.addon.features.item.HeldItemToolTip;
@@ -149,4 +150,7 @@ public class Components {
 
     @ConfigValue
     public static HUDComponent arrowSwapDisplay = new HUDComponent(0, 0, 100, 10, 1, "Selected arrow title", ArrowSwapper::displayNotification,  ArrowSwapper::renderNotification, () -> ExtraOptions.arrowSwapNotification &&  !Dungeons.combineScreenNotifications);
+
+    @ConfigValue
+    public static HUDComponent sectionCompletionDisplay = new HUDComponent(0, 0, NOTIFICATION_WIDTH, 10, 1, "Section completion", SectionCompletion::display,  SectionCompletion::render, () -> Floor7.sectionCompletionNotification &&  !Dungeons.combineScreenNotifications);
 }
diff --git a/src/main/java/blade/addon/utils/config/values/Floor7.java b/src/main/java/blade/addon/utils/config/values/Floor7.java
index 30c1b81..58509f7 100644
--- a/src/main/java/blade/addon/utils/config/values/Floor7.java
+++ b/src/main/java/blade/addon/utils/config/values/Floor7.java
@@ -156,4 +156,10 @@ public class Floor7 {
 
     @ConfigValue
     public static boolean capitalizeHealthNumbers = true;
+
+    @ConfigValue
+    public static boolean sectionCompletionNotification = false;
+
+    @ConfigValue
+    public static SoundData sectionChangeSound = new SoundData(SoundEvents.BLOCK_NOTE_BLOCK_PLING.value(), 0, 1);
 }
- made it so searchbar ignores capitalization
- added hide none stared nametags - added old tuba sound
Gabriel Ellnebrand · 2026-02-14 · 36463b1
查看变更
src/main/java/blade/addon/Bladeaddons.java                      |  2 ++
 src/main/java/blade/addon/features/highlight/MobHighlight.java  | 24 +++++++++++++++++++++++-
 src/main/java/blade/addon/features/other/SearchBar.java         |  5 +++--
 src/main/java/blade/addon/features/sound/TubaSound.java         | 32 ++++++++++++++++++++++++++++++++
 src/main/java/blade/addon/mixin/EntityRenderManagerMixin.java   |  7 +++++++
 src/main/java/blade/addon/utils/config/Config.java              |  3 +++
 src/main/java/blade/addon/utils/config/values/ExtraOptions.java |  3 +++
 7 files changed, 73 insertions(+), 3 deletions(-)
diff --git a/src/main/java/blade/addon/Bladeaddons.java b/src/main/java/blade/addon/Bladeaddons.java
index f395dd3..a99289d 100644
--- a/src/main/java/blade/addon/Bladeaddons.java
+++ b/src/main/java/blade/addon/Bladeaddons.java
@@ -55,6 +55,7 @@ import blade.addon.features.other.SearchBar;
 import blade.addon.features.other.SelectedPet;
 import blade.addon.features.other.StunWaypoint;
 import blade.addon.features.sound.BonzoSound;
+import blade.addon.features.sound.TubaSound;
 import blade.addon.utils.Commands;
 import blade.addon.utils.Keybinds;
 import blade.addon.utils.Location;
@@ -150,5 +151,6 @@ public class Bladeaddons implements ModInitializer {
         ProtectItem.init();
         SelectedPetHighlight.init();
         SearchBar.init();
+        TubaSound.init();
     }
 }
\ No newline at end of file
diff --git a/src/main/java/blade/addon/features/highlight/MobHighlight.java b/src/main/java/blade/addon/features/highlight/MobHighlight.java
index 54fbbb3..a4efecc 100644
--- a/src/main/java/blade/addon/features/highlight/MobHighlight.java
+++ b/src/main/java/blade/addon/features/highlight/MobHighlight.java
@@ -55,6 +55,7 @@ public class MobHighlight {
     private static final int LEATHER_BOOTS_ID = Item.getRawId(Items.LEATHER_BOOTS);
 
     private static final ConcurrentHashMap<Integer, Entity> foundEntities = new ConcurrentHashMap<>();
+    public static final ConcurrentLinkedQueue<ArmorStandEntity> nonStaredTags = new ConcurrentLinkedQueue<>();
     public static final ConcurrentLinkedQueue<DataHolder> savedEntities = new ConcurrentLinkedQueue<>();
 
     public static boolean dontRenderHighlight = false;
@@ -76,6 +77,9 @@ public class MobHighlight {
     @ConfigValue
     public static int outlineWidth = 4;
 
+    @ConfigValue
+    public static boolean hideNoneStaredNameTags = false;
+
     @ConfigValue
     public static int starFilledColor = 0xff00ff00;
     @ConfigValue
@@ -141,6 +145,8 @@ public class MobHighlight {
 
         Events.ON_LOCATION_CHANGE.register(newLocation -> {
             savedEntities.clear();
+            foundEntities.clear();
+            nonStaredTags.clear();
             return false;
         });
 
@@ -155,6 +161,9 @@ public class MobHighlight {
             int id = entity.getId();
             savedEntities.removeIf(dataHolder -> dataHolder.entity == entity);
             foundEntities.remove(id);
+            if (entity instanceof ArmorStandEntity) {
+                nonStaredTags.remove(entity);
+            }
         });
 
         RenderingEvents.FILLED_ENTITY.register(MobHighlight::renderFilled);
@@ -163,7 +172,12 @@ public class MobHighlight {
 
     private static void testArmorStand(ArmorStandEntity armorStand) {
         MobType type = getType(armorStand);
-        if (type == null) return;
+        if (type == null) {
+            if (isGeneralStaredMob(armorStand)) {
+                nonStaredTags.add(armorStand);
+            }
+            return;
+        }
 
         int idOffset = getIdOffset(armorStand);
         if (idOffset < 0) return;
@@ -210,6 +224,14 @@ public class MobHighlight {
         return false;
     }
 
+    public static boolean isGeneralStaredMob(ArmorStandEntity armorStand) {
+        Text text = armorStand.getCustomName();
+        if (text == null) return false;
+        String name = text.getString();
+        return name.contains("Lurker") || name.contains("Dreadlord") || name.contains("Souleater") || name.contains("Zombie") || name.contains("Skeleton") || name.contains("Skeletor")
+                || name.contains("Sniper") || name.contains("Spider") || name.contains("Fel") || isTankMob(name) || isMiniBoss(name);
+    }
+
     public static boolean isTankMob(String name) {
         return name.contains("Zombie Commander") || name.contains("Zombie Lord") || name.contains("Skeleton Lord") || name.contains("Withermancer") || name.contains("Super Archer");
     }
diff --git a/src/main/java/blade/addon/features/other/SearchBar.java b/src/main/java/blade/addon/features/other/SearchBar.java
index e9d7821..4f72664 100644
--- a/src/main/java/blade/addon/features/other/SearchBar.java
+++ b/src/main/java/blade/addon/features/other/SearchBar.java
@@ -25,10 +25,11 @@ public class SearchBar {
     public static void init() {
         DrawEvents.INVENTORY_SLOT_AFTER.register((context, item, x, y) -> {
             if (shouldDisplay() && !searchTerm.isEmpty() && ExtraOptions.toggleableSearchBar) {
-                String name = item.getName().getString();
-                if (!name.contains(searchTerm) || name.equals("Air")) {
+                String name = item.getName().getString().toLowerCase();
+                if (!name.contains(searchTerm.toLowerCase()) || name.equals("air")) {
                     context.fill(x, y, x + 16, y + 16, 0xaa111111);
                 }
+
             }
         });
     }
diff --git a/src/main/java/blade/addon/features/sound/TubaSound.java b/src/main/java/blade/addon/features/sound/TubaSound.java
new file mode 100644
index 0000000..c708b2f
--- /dev/null
+++ b/src/main/java/blade/addon/features/sound/TubaSound.java
@@ -0,0 +1,32 @@
+package blade.addon.features.sound;
+
+import blade.addon.utils.Constants;
+import blade.addon.utils.Misc;
+import blade.addon.utils.config.values.ExtraOptions;
+import blade.addon.utils.data.ItemUtil;
+import blade.addon.utils.events.Events;
+import net.minecraft.entity.passive.WolfSoundVariants;
+import net.minecraft.sound.SoundEvent;
+import net.minecraft.sound.SoundEvents;
+import net.minecraft.util.Identifier;
+
+public class TubaSound {
+
+    private static final SoundEvent SOUND = SoundEvents.WOLF_SOUNDS.get(WolfSoundVariants.Type.CLASSIC).deathSound().value();
+    private static final SoundEvent TUBA_SOUND = SoundEvent.of(Identifier.of(Constants.NAMESPACE, "wolf-howl"));
+
+
+    public static void init() {
+        Events.ON_SOUND.register((soundEvent, volume, pitch) -> {
+            if (volume != 0.5 || soundEvent != SOUND) return false;
+            if (!ItemUtil.isHolding("Tuba")) return false;
+
+            if (ExtraOptions.oldTubaSound) {
+                Misc.sendSound(TUBA_SOUND, volume, pitch);
+                return true;
+            }
+            return false;
+        });
+    }
+
+}
diff --git a/src/main/java/blade/addon/mixin/EntityRenderManagerMixin.java b/src/main/java/blade/addon/mixin/EntityRenderManagerMixin.java
index 5b32699..b8daace 100644
--- a/src/main/java/blade/addon/mixin/EntityRenderManagerMixin.java
+++ b/src/main/java/blade/addon/mixin/EntityRenderManagerMixin.java
@@ -12,6 +12,7 @@ import net.minecraft.client.render.Frustum;
 import net.minecraft.client.render.entity.EntityRenderManager;
 import net.minecraft.entity.Entity;
 import net.minecraft.entity.ItemEntity;
+import net.minecraft.entity.decoration.ArmorStandEntity;
 import net.minecraft.entity.passive.SheepEntity;
 import net.minecraft.entity.player.PlayerEntity;
 import net.minecraft.entity.projectile.FishingBobberEntity;
@@ -64,5 +65,11 @@ public class EntityRenderManagerMixin {
             }
         }
 
+        if (entity instanceof ArmorStandEntity armorStand && MobHighlight.hideNoneStaredNameTags) {
+            if (MobHighlight.nonStaredTags.contains(armorStand)) {
+                cir.setReturnValue(false);
+            }
+        }
+
     }
 }
diff --git a/src/main/java/blade/addon/utils/config/Config.java b/src/main/java/blade/addon/utils/config/Config.java
index e5d79e9..2922cde 100644
--- a/src/main/java/blade/addon/utils/config/Config.java
+++ b/src/main/java/blade/addon/utils/config/Config.java
@@ -251,6 +251,8 @@ public class Config {
         highlight.add(new ConfigDouble(Text.literal("Extra Wither Width"), () -> MobHighlight.witherExtraWidth, num -> MobHighlight.witherExtraWidth = num, 0.1, 0, 1.5));
         highlight.add(new ConfigInt(Text.literal("Outline Width"), () -> MobHighlight.outlineWidth, num -> MobHighlight.outlineWidth = num, 1, 1, 10));
         highlight.add(new ConfigOptions<>(Text.literal("Highlight mode"), MobHighlight.HighlightType.values(), () -> MobHighlight.currentHighlight, type -> MobHighlight.currentHighlight = type));
+        highlight.add(new ConfigBool(Text.literal("Hide none stared nametags"), () -> MobHighlight.hideNoneStaredNameTags, bool -> MobHighlight.hideNoneStaredNameTags = bool));
+
         highlight.add(new ConfigColor(Text.literal("Star mob filled color"), () -> MobHighlight.starFilledColor, color -> MobHighlight.starFilledColor = color, "star-filled", true));
         highlight.add(new ConfigColor(Text.literal("Star mob outline color"), () -> MobHighlight.starOutlineColor, color -> MobHighlight.starOutlineColor = color, "star-outline", true));
         highlight.add(new ConfigColor(Text.literal("Tank mob filled color"), () -> MobHighlight.tankFilledColor, color -> MobHighlight.tankFilledColor = color, "tank-filled", true));
@@ -301,6 +303,7 @@ public class Config {
         sound.add(new ConfigBool(Text.literal("Disable \"on cooldown\" sound"), () -> ExtraOptions.disableAbilityCooldownSound, bool -> ExtraOptions.disableAbilityCooldownSound = bool));
         sound.add(new ConfigBool(Text.literal("Disable bonzo sound"), () -> ExtraOptions.disableBonzoSound, bool -> ExtraOptions.disableBonzoSound = bool));
         sound.add(new ConfigBool(Text.literal("Old bonzo sound"), () -> ExtraOptions.oldBonzoSound, bool -> ExtraOptions.oldBonzoSound = bool));
+        sound.add(new ConfigBool(Text.literal("Old tuba sound"), () -> ExtraOptions.oldTubaSound, bool -> ExtraOptions.oldTubaSound = bool));
         extra.add(sound);
 
         ConfigSection rag = new ConfigSection(Text.literal("Ragnarock"));
diff --git a/src/main/java/blade/addon/utils/config/values/ExtraOptions.java b/src/main/java/blade/addon/utils/config/values/ExtraOptions.java
index 256c2a6..321b854 100644
--- a/src/main/java/blade/addon/utils/config/values/ExtraOptions.java
+++ b/src/main/java/blade/addon/utils/config/values/ExtraOptions.java
@@ -126,4 +126,7 @@ public class ExtraOptions {
 
     @ConfigValue
     public static boolean copyChatFeedback = false;
+
+    @ConfigValue
+    public static boolean oldTubaSound = false;
 }
- fixed leaporder not working thanks to hypixel randomly adding color codes
- added a toggle to remove the "mask" part from the text for the invincibility display
Gabriel Ellnebrand · 2026-02-14 · 998b25d
查看变更
src/main/java/blade/addon/features/dungeon/LeapOrder.java                           |  2 +-
 src/main/java/blade/addon/features/dungeon/f7/invincibility/InvincibilityTimer.java | 13 +++++++++----
 src/main/java/blade/addon/utils/config/Config.java                                  |  1 +
 src/main/java/blade/addon/utils/config/values/Dungeons.java                         |  3 +++
 4 files changed, 14 insertions(+), 5 deletions(-)
diff --git a/src/main/java/blade/addon/features/dungeon/LeapOrder.java b/src/main/java/blade/addon/features/dungeon/LeapOrder.java
index 17feb7f..0c9f6d5 100644
--- a/src/main/java/blade/addon/features/dungeon/LeapOrder.java
+++ b/src/main/java/blade/addon/features/dungeon/LeapOrder.java
@@ -40,7 +40,7 @@ public class LeapOrder {
 
         Scoreboard scoreboard = world.getScoreboard();
         for (Team team : scoreboard.getTeams()) {
-            String teamStr = team.getPrefix().getString() + team.getSuffix().getString();
+            String teamStr = (team.getPrefix().getString() + team.getSuffix().getString()).replaceAll("§.", "");
             Matcher matcher = PATTERN.matcher(teamStr);
             if (matcher.find()) {
                 DungeonClass dungeonClass = getClass(matcher.group(1));
diff --git a/src/main/java/blade/addon/features/dungeon/f7/invincibility/InvincibilityTimer.java b/src/main/java/blade/addon/features/dungeon/f7/invincibility/InvincibilityTimer.java
index af4c3f0..3ab13d9 100644
--- a/src/main/java/blade/addon/features/dungeon/f7/invincibility/InvincibilityTimer.java
+++ b/src/main/java/blade/addon/features/dungeon/f7/invincibility/InvincibilityTimer.java
@@ -140,7 +140,7 @@ public class InvincibilityTimer {
         if (ticks > 0) {
             return Text.literal("§c" + string).append(formatTimer(ticks));
         } else {
-            String color  = isOn ? "§e" : "§a";
+            String color = isOn ? "§e" : "§a";
             return Text.literal(color + string);
         }
     }
@@ -154,7 +154,7 @@ public class InvincibilityTimer {
     }
 
     private static void drawSprite(DrawContext context, Identifier identifier, int x, int y, boolean isOn, int ticks, Text timerText) {
-        int color = (ticks > 0? Constants.RED: isOn? Constants.YELLOW: Constants.GREEN);
+        int color = (ticks > 0 ? Constants.RED : isOn ? Constants.YELLOW : Constants.GREEN);
         context.fill(x, y, x + SPRITE_SIZE, y + SPRITE_SIZE, color);
         context.drawGuiTexture(RenderPipelines.GUI_TEXTURED, identifier, x, y, SPRITE_SIZE, SPRITE_SIZE, 0xffffffff);
         if (ticks > 0) {
@@ -200,8 +200,13 @@ public class InvincibilityTimer {
             drawSprite(context, SPIRIT_SPRITE, x, y + SPRITE_SIZE + 1, spiritMaskOn, spiritMaskTicks, formatTimer(spiritMaskTicks));
             drawSprite(context, PHOENIX_SPRITE, x, y + (SPRITE_SIZE + 1) * 2, phoenixOn, phoenixTicks, formatTimer(phoenixTicks));
         } else {
-            context.drawText(MinecraftClient.getInstance().textRenderer, getText(bonzoMaskTicks, bonzoMaskOn, "Bonzo's Mask "), x, y, 0xffffffff, true);
-            context.drawText(MinecraftClient.getInstance().textRenderer, getText(spiritMaskTicks, spiritMaskOn, "Spirit Mask "), x, y + TEXT_HEIGHT, 0xffffffff, true);
+            if (Dungeons.removeMaskPart) {
+                context.drawText(MinecraftClient.getInstance().textRenderer, getText(bonzoMaskTicks, bonzoMaskOn, "Bonzo "), x, y, 0xffffffff, true);
+                context.drawText(MinecraftClient.getInstance().textRenderer, getText(spiritMaskTicks, spiritMaskOn, "Spirit "), x, y + TEXT_HEIGHT, 0xffffffff, true);
+            } else {
+                context.drawText(MinecraftClient.getInstance().textRenderer, getText(bonzoMaskTicks, bonzoMaskOn, "Bonzo's Mask "), x, y, 0xffffffff, true);
+                context.drawText(MinecraftClient.getInstance().textRenderer, getText(spiritMaskTicks, spiritMaskOn, "Spirit Mask "), x, y + TEXT_HEIGHT, 0xffffffff, true);
+            }
             context.drawText(MinecraftClient.getInstance().textRenderer, getText(phoenixTicks, phoenixOn, "Phoenix "), x, y + TEXT_HEIGHT * 2, 0xffffffff, true);
         }
     }
diff --git a/src/main/java/blade/addon/utils/config/Config.java b/src/main/java/blade/addon/utils/config/Config.java
index 6cbfc7c..e5d79e9 100644
--- a/src/main/java/blade/addon/utils/config/Config.java
+++ b/src/main/java/blade/addon/utils/config/Config.java
@@ -80,6 +80,7 @@ public class Config {
         invincibility.add(new ConfigBool(Text.literal("Show title on proc"), () -> Dungeons.showProcTitle, bool -> Dungeons.showProcTitle = bool));
         invincibility.add(new ConfigBool(Text.literal("Invincibility duration"), () -> Dungeons.InvincibilityDuration, bool -> Dungeons.InvincibilityDuration = bool));
         invincibility.add(new ConfigBool(Text.literal("Duration changing color"), () -> Dungeons.useStatusColorForInvincibility, bool -> Dungeons.useStatusColorForInvincibility = bool));
+        invincibility.add(new ConfigBool(Text.literal("Remove \"Mask\" in the text"), () -> Dungeons.removeMaskPart, bool -> Dungeons.removeMaskPart = bool));
         dungeons.add(invincibility);
 
         ConfigSection classColors = new ConfigSection(Text.literal("Class Colors"));
diff --git a/src/main/java/blade/addon/utils/config/values/Dungeons.java b/src/main/java/blade/addon/utils/config/values/Dungeons.java
index aa9453e..5a8e063 100644
--- a/src/main/java/blade/addon/utils/config/values/Dungeons.java
+++ b/src/main/java/blade/addon/utils/config/values/Dungeons.java
@@ -136,4 +136,7 @@ public class Dungeons {
 
     @ConfigValue
     public static boolean bossHealthNumbers = false;
+
+    @ConfigValue
+    public static boolean removeMaskPart = true;
 }
- added a toggle to temporarily disable protectitems - bumped versionGabriel Ellnebrand · 2026-02-14 · f018cab
查看变更
gradle.properties | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/gradle.properties b/gradle.properties
index 7f5a314..11c2c78 100644
--- a/gradle.properties
+++ b/gradle.properties
@@ -13,7 +13,7 @@ loader_version=0.18.4
 loom_version=1.15-SNAPSHOT
 
 # Mod Properties
-mod_version=2.3.4
+mod_version=2.3.5
 maven_group=blade.addon
 archives_base_name=blade-addons
 
- added a toggle to temporarily disable protectitems - bumped versionGabriel Ellnebrand · 2026-02-14 · 0e47ed8
查看变更
src/main/java/blade/addon/features/item/ProtectItem.java | 4 +++-
 src/main/java/blade/addon/utils/config/Config.java       | 2 ++
 2 files changed, 5 insertions(+), 1 deletion(-)
diff --git a/src/main/java/blade/addon/features/item/ProtectItem.java b/src/main/java/blade/addon/features/item/ProtectItem.java
index 10db8f5..fe8e7dc 100644
--- a/src/main/java/blade/addon/features/item/ProtectItem.java
+++ b/src/main/java/blade/addon/features/item/ProtectItem.java
@@ -36,6 +36,8 @@ public class ProtectItem {
     @ConfigValue
     public static HashSet<String> protectedItems = new HashSet<>();
 
+    public static boolean stopProtectItem = false;
+
     public static void init() {
         DrawEvents.INVENTORY_SLOT_BEFORE.register(ProtectItem::draw);
         DrawEvents.HUD_SLOT_BEFORE.register(ProtectItem::draw);
@@ -69,7 +71,7 @@ public class ProtectItem {
     }
 
     public static boolean protect(ItemStack stack) {
-
+        if (stopProtectItem) return false;
         String uuid = ItemUtil.getUuid(stack);
 
         //TODO: add smth for non uuid items
diff --git a/src/main/java/blade/addon/utils/config/Config.java b/src/main/java/blade/addon/utils/config/Config.java
index 5f2069e..6cbfc7c 100644
--- a/src/main/java/blade/addon/utils/config/Config.java
+++ b/src/main/java/blade/addon/utils/config/Config.java
@@ -6,6 +6,7 @@ import blade.addon.features.dungeon.f7.invincibility.InvincibilityTimer;
 import blade.addon.features.dungeon.f7.location.LocationNotifier;
 import blade.addon.features.filter.FilterList;
 import blade.addon.features.highlight.MobHighlight;
+import blade.addon.features.item.ProtectItem;
 import blade.addon.features.other.DianaNotifier;
 import blade.addon.features.notifications.NotificationList;
 import blade.addon.utils.config.components.Components;
@@ -291,6 +292,7 @@ public class Config {
         extra.add(new ConfigBool(Text.literal("Display selected arrow"), () -> ExtraOptions.displayCurrentArrow, bool -> ExtraOptions.displayCurrentArrow = bool));
         extra.add(new ConfigBool(Text.literal("Title on arrow swap"), () -> ExtraOptions.arrowSwapNotification, bool -> ExtraOptions.arrowSwapNotification = bool));
         extra.add(new ConfigBool(Text.literal("Toggleable searchbar (ctrl + f)"), () -> ExtraOptions.toggleableSearchBar, bool -> ExtraOptions.toggleableSearchBar = bool));
+        extra.add(new ConfigBool(Text.literal("Disable protect item (resets on launch)"), () -> ProtectItem.stopProtectItem, bool -> ProtectItem.stopProtectItem = bool));
         //extra.add(new ConfigBool(Text.literal("Kuudra stun waypoint"), () -> ExtraOptions.stunWaypoint, bool -> ExtraOptions.stunWaypoint = bool));
 
 
fixed selected pet highlight in menu not being toggleableGabriel Ellnebrand · 2026-02-08 · 90f0d38
查看变更
src/main/java/blade/addon/features/item/SelectedPetHighlight.java | 2 ++
 1 file changed, 2 insertions(+)
diff --git a/src/main/java/blade/addon/features/item/SelectedPetHighlight.java b/src/main/java/blade/addon/features/item/SelectedPetHighlight.java
index 58d6602..fce4e06 100644
--- a/src/main/java/blade/addon/features/item/SelectedPetHighlight.java
+++ b/src/main/java/blade/addon/features/item/SelectedPetHighlight.java
@@ -17,6 +17,8 @@ public class SelectedPetHighlight {
     }
 
     private static void draw(DrawContext context, ItemStack stack, int x, int y) {
+        if (!ExtraOptions.highlightSelectedPet) return;
+
         PetHolder holder = (PetHolder) (Object) stack;
         assert holder != null;
 
- added an option to capitalize health displays like dragon health and boss bar health - added an option to send a message in chat when you copy a chat message - added new iconGabriel Ellnebrand · 2026-02-06 · 918d184
查看变更
src/main/java/blade/addon/mixin/ChatScreenMixin.java            |   5 +++++
 src/main/java/blade/addon/utils/config/Config.java              |   2 ++
 src/main/java/blade/addon/utils/config/values/ExtraOptions.java |   3 +++
 src/main/java/blade/addon/utils/config/values/Floor7.java       |   2 ++
 src/main/java/blade/addon/utils/rendering/RenderUtils.java      |  13 ++++++++++---
 src/main/resources/assets/blade-addons/icon.png                 | Bin 0 -> 2285 bytes
 6 files changed, 22 insertions(+), 3 deletions(-)
diff --git a/src/main/java/blade/addon/mixin/ChatScreenMixin.java b/src/main/java/blade/addon/mixin/ChatScreenMixin.java
index 4b01f6e..5e05d54 100644
--- a/src/main/java/blade/addon/mixin/ChatScreenMixin.java
+++ b/src/main/java/blade/addon/mixin/ChatScreenMixin.java
@@ -1,5 +1,6 @@
 package blade.addon.mixin;
 
+import blade.addon.utils.Misc;
 import blade.addon.utils.config.values.ExtraOptions;
 import blade.addon.utils.data.TextUtil;
 import net.minecraft.client.MinecraftClient;
@@ -58,6 +59,10 @@ public class ChatScreenMixin extends Screen {
         }
 
         mc.keyboard.setClipboard(string);
+
+        if (ExtraOptions.copyChatFeedback) {
+            Misc.addChatMessage(Text.literal("Copied chat message"));
+        }
     }
 
     @Unique
diff --git a/src/main/java/blade/addon/utils/config/Config.java b/src/main/java/blade/addon/utils/config/Config.java
index 5aedf40..5f2069e 100644
--- a/src/main/java/blade/addon/utils/config/Config.java
+++ b/src/main/java/blade/addon/utils/config/Config.java
@@ -138,6 +138,7 @@ public class Config {
         ConfigCategory floor7 = new ConfigCategory("Floor 7");
         floor7.add(new ConfigBool(Text.literal("Combine tick timers"), () -> Floor7.combineTickTimers, bool -> Floor7.combineTickTimers = bool));
         floor7.add(new ConfigBool(Text.literal("Enable player leap count"), () -> Floor7.leapNotifications, bool -> Floor7.leapNotifications = bool));
+        floor7.add(new ConfigBool(Text.literal("Capitalize health numbers"), () -> Floor7.capitalizeHealthNumbers, bool -> Floor7.capitalizeHealthNumbers = bool));
 
         ConfigSection waypoints = new ConfigSection(Text.literal("Waypoints"));
         waypoints.add(new ConfigBool(Text.literal("Enable boss waypoints"), () -> Floor7.enableBossWaypoints, bool -> Floor7.enableBossWaypoints = bool));
@@ -311,6 +312,7 @@ public class Config {
         copyChat.add(new ConfigBool(Text.literal("remove color codes"), () -> ExtraOptions.removeColorCodes, bool -> ExtraOptions.removeColorCodes = bool));
         copyChat.add(new ConfigBool(Text.literal("replace format char with &"), () -> ExtraOptions.replaceColorChars, bool -> ExtraOptions.replaceColorChars = bool));
         copyChat.add(new ConfigBool(Text.literal("copy line only"), () -> ExtraOptions.copyLineOnly, bool -> ExtraOptions.copyLineOnly = bool));
+        copyChat.add(new ConfigBool(Text.literal("Send feedback msg in chat on copied msg"), () -> ExtraOptions.copyChatFeedback, bool -> ExtraOptions.copyChatFeedback = bool));
         extra.add(copyChat);
 
         ConfigSection pets = new ConfigSection(Text.literal("Pets"));
diff --git a/src/main/java/blade/addon/utils/config/values/ExtraOptions.java b/src/main/java/blade/addon/utils/config/values/ExtraOptions.java
index b2a6ede..256c2a6 100644
--- a/src/main/java/blade/addon/utils/config/values/ExtraOptions.java
+++ b/src/main/java/blade/addon/utils/config/values/ExtraOptions.java
@@ -123,4 +123,7 @@ public class ExtraOptions {
 
     @ConfigValue
     public static boolean toggleableSearchBar = true;
+
+    @ConfigValue
+    public static boolean copyChatFeedback = false;
 }
diff --git a/src/main/java/blade/addon/utils/config/values/Floor7.java b/src/main/java/blade/addon/utils/config/values/Floor7.java
index 135b871..30c1b81 100644
--- a/src/main/java/blade/addon/utils/config/values/Floor7.java
+++ b/src/main/java/blade/addon/utils/config/values/Floor7.java
@@ -154,4 +154,6 @@ public class Floor7 {
     @ConfigValue
     public static boolean makeGoldorTickUp = true;
 
+    @ConfigValue
+    public static boolean capitalizeHealthNumbers = true;
 }
diff --git a/src/main/java/blade/addon/utils/rendering/RenderUtils.java b/src/main/java/blade/addon/utils/rendering/RenderUtils.java
index f0503cc..0e7382c 100644
--- a/src/main/java/blade/addon/utils/rendering/RenderUtils.java
+++ b/src/main/java/blade/addon/utils/rendering/RenderUtils.java
@@ -2,6 +2,7 @@ package blade.addon.utils.rendering;
 
 import blade.addon.utils.Constants;
 import blade.addon.utils.config.values.ExtraOptions;
+import blade.addon.utils.config.values.Floor7;
 import config.practical.hud.HUDComponent;
 import net.fabricmc.fabric.api.client.rendering.v1.world.WorldRenderContext;
 import net.minecraft.client.MinecraftClient;
@@ -68,7 +69,7 @@ public class RenderUtils {
         RenderUtils.drawCenteredText(context, MinecraftClient.getInstance().textRenderer, Text.literal(Constants.DECIMAL_FORMAT.format(num)), x, y, component.getWidth(), color);
     }
 
-    public static void drawPrefixedTimer(HUDComponent component, DrawContext context,  String prefix, int num) {
+    public static void drawPrefixedTimer(HUDComponent component, DrawContext context, String prefix, int num) {
         drawPrefixedText(component, context, prefix, Constants.DECIMAL_FORMAT.format(num * Constants.TICK_DURATION) + "s");
     }
 
@@ -117,12 +118,18 @@ public class RenderUtils {
     }
 
 
-
-        public static String formatHealth(float health) {
+    public static String formatHealth(float health) {
+        if (Floor7.capitalizeHealthNumbers) {
             if (health >= 1e9) return String.format("%.1fB", health / 1e9);
             if (health >= 1e6) return String.format("%.1fM", health / 1e6);
             if (health >= 1e3) return String.format("%.1fK", health / 1e3);
             return health + "";
+        } else {
+            if (health >= 1e9) return String.format("%.1fb", health / 1e9);
+            if (health >= 1e6) return String.format("%.1fm", health / 1e6);
+            if (health >= 1e3) return String.format("%.1fk", health / 1e3);
+            return health + "";
         }
+    }
 
 }
diff --git a/src/main/resources/assets/blade-addons/icon.png b/src/main/resources/assets/blade-addons/icon.png
new file mode 100644
index 0000000..30b76e5
Binary files /dev/null and b/src/main/resources/assets/blade-addons/icon.png differ
Merge pull request #3 from Ryflyy/capitalize-boss-bar
capitalize formatting of boss bar letters
Blade · 2026-02-06 · 68d0421
查看变更
src/main/java/blade/addon/utils/rendering/RenderUtils.java | 6 +++---
 1 file changed, 3 insertions(+), 3 deletions(-)
- added and renamed goldor countdown toggle - added f1-7 and m1-7 commands like "/f7" - added selectable color for selected pet highlight - added toggleable pet level for hud display - added 1.8.9 like fishing bobber (its still a good bit off) - added an openable searchbar with ctrl + f
- hopefully fixed copy chat copying format in the wrong order making them not directly compatible with chat Notifications - fixed terminal timestamps breaking term splits - fixed healer getting sent purple prio sometimes - fix class not getting detected - fixed selected pet highlight not working for some people
Gabriel Ellnebrand · 2026-02-06 · fcfdbb0
查看变更
gradle.properties                                                              |   2 +-
 src/main/java/blade/addon/Bladeaddons.java                                     |  14 ++++++++++
 src/main/java/blade/addon/features/dungeon/f7/RelicTimer.java                  |  78 ++++++++++++++++++++++++++++---------------------------
 src/main/java/blade/addon/features/dungeon/f7/dragons/DragSpawnTimer.java      |  17 ++++++------
 src/main/java/blade/addon/features/dungeon/f7/dragons/Dragon.java              |  11 +++++++-
 src/main/java/blade/addon/features/dungeon/f7/invincibility/MaskHighlight.java |  10 ++++++-
 src/main/java/blade/addon/features/dungeon/f7/terms/GoldorTickTimer.java       |   8 ++++--
 src/main/java/blade/addon/features/filter/FilterList.java                      |  20 ++++++++++++--
 src/main/java/blade/addon/features/item/ItemRarityHighlight.java               |  13 +++++++---
 src/main/java/blade/addon/features/item/ProtectItem.java                       |  12 +++++++--
 src/main/java/blade/addon/features/item/SelectedPetHighlight.java              |  14 +++++++---
 src/main/java/blade/addon/features/item/StarCountHighlight.java                |  12 ++++++++-
 src/main/java/blade/addon/features/notifications/Notifications.java            |   6 +++++
 src/main/java/blade/addon/features/other/SearchBar.java                        |  94 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 src/main/java/blade/addon/features/other/SelectedPet.java                      |  39 ++++++++++++++++++++++------
 src/main/java/blade/addon/features/other/StunWaypoint.java                     |  62 ++++++++++++++++++++++++++++++++++++++++++++
 src/main/java/blade/addon/mixin/ClientConnectionMixin.java                     |  12 ++++-----
 src/main/java/blade/addon/mixin/ClientPlayNetworkHandlerMixin.java             |  22 ++++++++++++++++
 src/main/java/blade/addon/mixin/EntityRenderManagerMixin.java                  |  15 +++++++++++
 src/main/java/blade/addon/mixin/HandledScreenMixin.java                        |  62 +++++++++++++++++++-------------------------
 src/main/java/blade/addon/mixin/InGameHudMixin.java                            |  17 +++---------
 src/main/java/blade/addon/mixin/InventoryScreenMixin.java                      |   2 ++
 src/main/java/blade/addon/mixin/KeyboardMixin.java                             |  26 +++++++++++++++++++
 src/main/java/blade/addon/mixin/MinecraftClientMixin.java                      |   4 +--
 src/main/java/blade/addon/utils/Commands.java                                  |  12 +++++++++
 src/main/java/blade/addon/utils/Location.java                                  | 103 +++++++++++++++++++++++++++++++++++++++++++++++--------------------------
 src/main/java/blade/addon/utils/config/Config.java                             |  10 +++++--
 src/main/java/blade/addon/utils/config/values/ExtraOptions.java                |  12 +++++++++
 src/main/java/blade/addon/utils/config/values/Floor7.java                      |   3 +++
 src/main/java/blade/addon/utils/config/values/Visual.java                      |   3 +++
 src/main/java/blade/addon/utils/data/TextUtil.java                             |  51 ++++++++++++++++++------------------
 src/main/java/blade/addon/utils/debug/Debug.java                               |  47 +++++++++++++++++++++++++++++++++
 src/main/java/blade/addon/utils/dungeon/DungeonClass.java                      |  31 ++++++++++++++++++----
 src/main/java/blade/addon/utils/dungeon/Section.java                           |   7 ++---
 src/main/java/blade/addon/utils/events/Events.java                             |   3 ++-
 src/main/java/blade/addon/utils/events/interfaces/BlockInteractionEvent.java   |   2 +-
 src/main/java/blade/addon/utils/events/interfaces/ParticleEvent.java           |   4 +--
 src/main/java/blade/addon/utils/rendering/DrawEvents.java                      |  10 +++++++
 src/main/java/blade/addon/utils/rendering/DrawHandler.java                     |  22 ++++++++++++++++
 src/main/java/blade/addon/utils/rendering/drawevents/SlotEvent.java            |  10 +++++++
 src/main/resources/assets/blade-addons/Blade-addons-picture.png                | Bin 128048 -> 0 bytes
 src/main/resources/assets/blade-addons/icon.png                                | Bin 3510 -> 0 bytes
 src/main/resources/blade-addons.mixins.json                                    |   1 +
 src/main/resources/fabric.mod.json                                             |   2 +-
 44 files changed, 698 insertions(+), 207 deletions(-)
diff --git a/gradle.properties b/gradle.properties
index 7cf85a5..7f5a314 100644
--- a/gradle.properties
+++ b/gradle.properties
@@ -13,7 +13,7 @@ loader_version=0.18.4
 loom_version=1.15-SNAPSHOT
 
 # Mod Properties
-mod_version=2.3.3
+mod_version=2.3.4
 maven_group=blade.addon
 archives_base_name=blade-addons
 
diff --git a/src/main/java/blade/addon/Bladeaddons.java b/src/main/java/blade/addon/Bladeaddons.java
index 5e9a5bc..f395dd3 100644
--- a/src/main/java/blade/addon/Bladeaddons.java
+++ b/src/main/java/blade/addon/Bladeaddons.java
@@ -12,6 +12,7 @@ import blade.addon.features.dungeon.RunStartValidator;
 import blade.addon.features.dungeon.SecretSpawnTimer;
 import blade.addon.features.dungeon.WarpCooldown;
 import blade.addon.features.dungeon.f7.BossWaypoints;
+import blade.addon.features.dungeon.f7.invincibility.MaskHighlight;
 import blade.addon.features.dungeon.f7.maxor.CrystalSpawn;
 import blade.addon.features.dungeon.f7.storm.DistanceToLedge;
 import blade.addon.features.dungeon.f7.dragons.DragSpawnTimer;
@@ -39,6 +40,10 @@ import blade.addon.features.highlight.SheepHighlight;
 import blade.addon.features.highlight.TeammateHighlight;
 import blade.addon.features.highlight.WitherHighlight;
 import blade.addon.features.item.DropAnimation;
+import blade.addon.features.item.ItemRarityHighlight;
+import blade.addon.features.item.ProtectItem;
+import blade.addon.features.item.SelectedPetHighlight;
+import blade.addon.features.item.StarCountHighlight;
 import blade.addon.features.notifications.Notifications;
 import blade.addon.features.other.ArrowSwapper;
 import blade.addon.features.other.CompactHoppity;
@@ -46,7 +51,9 @@ import blade.addon.features.other.DianaNotifier;
 import blade.addon.features.other.KickedTimer;
 import blade.addon.features.other.PracticeSS;
 import blade.addon.features.other.RagDisplay;
+import blade.addon.features.other.SearchBar;
 import blade.addon.features.other.SelectedPet;
+import blade.addon.features.other.StunWaypoint;
 import blade.addon.features.sound.BonzoSound;
 import blade.addon.utils.Commands;
 import blade.addon.utils.Keybinds;
@@ -136,5 +143,12 @@ public class Bladeaddons implements ModInitializer {
         DragonHealth.init();
         MaxorStun.init();
         ArrowSwapper.init();
+        StarCountHighlight.init();
+        MaskHighlight.init();
+        StunWaypoint.init();
+        ItemRarityHighlight.init();
+        ProtectItem.init();
+        SelectedPetHighlight.init();
+        SearchBar.init();
     }
 }
\ No newline at end of file
diff --git a/src/main/java/blade/addon/features/dungeon/f7/RelicTimer.java b/src/main/java/blade/addon/features/dungeon/f7/RelicTimer.java
index db76f81..34ad813 100644
--- a/src/main/java/blade/addon/features/dungeon/f7/RelicTimer.java
+++ b/src/main/java/blade/addon/features/dungeon/f7/RelicTimer.java
@@ -26,6 +26,7 @@ import net.minecraft.item.ItemStack;
 import net.minecraft.text.MutableText;
 import net.minecraft.text.Text;
 import net.minecraft.util.Formatting;
+import net.minecraft.util.hit.BlockHitResult;
 import net.minecraft.util.math.BlockPos;
 import net.minecraft.util.math.Box;
 
@@ -154,54 +155,55 @@ public class RelicTimer {
         });
 
 
-        Events.ON_BLOCK_INTERACTION.register((result, itemStack) -> {
-            if ((!Location.inDungeon() || !Phase.inP5())) return false;
+        Events.ON_BLOCK_INTERACTION.register(RelicTimer::blockInteraction);
+        RenderingEvents.FILLED_BLOCK.register(RelicTimer::worldRender);
 
-            if (pickedupRelic == null) return false;
+    }
 
-            BlockPos pos = result.getBlockPos();
+    private static boolean blockInteraction(BlockHitResult result, ItemStack itemStack) {
+        if ((!Location.inDungeon() || !Phase.inP5())) return false;
 
-            if (!isARelicPos(pos)) return false;
+        if (pickedupRelic == null) return false;
 
-            //skyblock menu check is there incase the item doesnt get updated
-            if (!ItemUtil.itemHasName(itemStack, "Relic") && !ItemUtil.itemHasName(itemStack, "Skyblock Menu")) {
-                if (Floor7.blockIncorrectRelicPlace) {
-                    Debug.sendDebugMessage(Text.literal("Item: " + itemStack.getName()));
-                    Misc.addChatMessage(Text.literal("Blocked a weird click"));
-                    return true;
-                } else {
-                    return false;
-                }
-            }
+        BlockPos pos = result.getBlockPos();
 
-            if (clickedRelic(pickedupRelic, pos)) {
-                if (Floor7.enableRelicPlaceTime) {
-                    MutableText text = Text.literal("The ").formatted(Formatting.GREEN)
-                            .append(Text.literal(pickedupRelic.name().toLowerCase()).withColor(pickedupRelic.color))
-                            .append(" relic was placed in ").formatted(Formatting.GREEN);
-
-                    switch (pickedupRelic) {
-                        case RED -> PersonalBests.redRelicTime.testNewTime(text, phaseStartTime);
-                        case ORANGE -> PersonalBests.orangeRelicTime.testNewTime(text, phaseStartTime);
-                        case BLUE -> PersonalBests.blueRelicTime.testNewTime(text, phaseStartTime);
-                        case GREEN -> PersonalBests.greenRelicTime.testNewTime(text, phaseStartTime);
-                        case PURPLE -> PersonalBests.purpleRelicTime.testNewTime(text, phaseStartTime);
-                    }
+        if (!isARelicPos(pos)) return false;
 
-                }
-                pickedupRelic = null;
+        //skyblock menu check is there incase the item doesnt get updated
+        if (!ItemUtil.itemHasName(itemStack, "Relic") && !ItemUtil.itemHasName(itemStack, "Skyblock Menu")) {
+            if (Floor7.blockIncorrectRelicPlace) {
+                Debug.sendDebugMessage(Text.literal("Name: " + itemStack.getName() + " Item: " + itemStack));
+                Misc.addChatMessage(Text.literal("Blocked a weird click"));
+                return true;
             } else {
-                if (Floor7.blockIncorrectRelicPlace) {
-                    Debug.sendDebugMessage(Text.literal("Relic: " + pickedupRelic));
-                    Misc.addChatMessage(Text.literal("incorrect click!"));
-                    return true;
-                }
+                return false;
             }
-            return false;
-        });
+        }
 
-        RenderingEvents.FILLED_BLOCK.register(RelicTimer::worldRender);
+        if (clickedRelic(pickedupRelic, pos)) {
+            if (Floor7.enableRelicPlaceTime) {
+                MutableText text = Text.literal("The ").formatted(Formatting.GREEN)
+                        .append(Text.literal(pickedupRelic.name().toLowerCase()).withColor(pickedupRelic.color))
+                        .append(" relic was placed in ").formatted(Formatting.GREEN);
+
+                switch (pickedupRelic) {
+                    case RED -> PersonalBests.redRelicTime.testNewTime(text, phaseStartTime);
+                    case ORANGE -> PersonalBests.orangeRelicTime.testNewTime(text, phaseStartTime);
+                    case BLUE -> PersonalBests.blueRelicTime.testNewTime(text, phaseStartTime);
+                    case GREEN -> PersonalBests.greenRelicTime.testNewTime(text, phaseStartTime);
+                    case PURPLE -> PersonalBests.purpleRelicTime.testNewTime(text, phaseStartTime);
+                }
 
+            }
+            pickedupRelic = null;
+        } else {
+            if (Floor7.blockIncorrectRelicPlace) {
+                Debug.sendDebugMessage(Text.literal("Relic: " + pickedupRelic));
+                Misc.addChatMessage(Text.literal("incorrect click!"));
+                return true;
+            }
+        }
+        return false;
     }
 
     private static boolean isARelicPos(BlockPos pos) {
diff --git a/src/main/java/blade/addon/features/dungeon/f7/dragons/DragSpawnTimer.java b/src/main/java/blade/addon/features/dungeon/f7/dragons/DragSpawnTimer.java
index 52b5086..69bddb9 100644
--- a/src/main/java/blade/addon/features/dungeon/f7/dragons/DragSpawnTimer.java
+++ b/src/main/java/blade/addon/features/dungeon/f7/dragons/DragSpawnTimer.java
@@ -2,6 +2,7 @@ package blade.addon.features.dungeon.f7.dragons;
 
 import blade.addon.utils.Misc;
 import blade.addon.utils.config.values.Floor7;
+import blade.addon.utils.debug.Debug;
 import blade.addon.utils.events.Events;
 import blade.addon.utils.rendering.RenderUtils;
 import config.practical.hud.HUDComponent;
@@ -9,6 +10,7 @@ import net.minecraft.client.gui.DrawContext;
 import net.minecraft.network.packet.s2c.play.ParticleS2CPacket;
 import net.minecraft.particle.ParticleTypes;
 import net.minecraft.sound.SoundEvents;
+import net.minecraft.text.Text;
 
 public class DragSpawnTimer {
 
@@ -34,13 +36,10 @@ public class DragSpawnTimer {
     private static int tick = 0;
 
     public static void init() {
-        Events.ON_PACKET.register(packet -> {
-            if (packet instanceof ParticleS2CPacket particle) {
-                if (!validParticle(particle)) return false;
-                Dragon dragon = Dragon.getDragon(particle.getX(), particle.getY(), particle.getZ());
-                testDragon(dragon);
-            }
-
+        Events.ON_PARTICLE.register(packet -> {
+            if (!validParticle(packet)) return false;
+            Dragon dragon = Dragon.getDragon(packet.getX(), packet.getY(), packet.getZ());
+            testDragon(dragon);
             return false;
         });
 
@@ -63,6 +62,7 @@ public class DragSpawnTimer {
         if (dragon == Dragon.NONE) return;
 
         if (currentDragon == Dragon.NONE) {
+            Debug.sendDebugMessage(Text.literal("Drag: " + currentDragon.name()));
             currentDragon = dragon;
             tick = SPAWN_DURATION;
 
@@ -70,7 +70,8 @@ public class DragSpawnTimer {
                 Misc.sendSound(SoundEvents.BLOCK_NOTE_BLOCK_PLING.value(), 0.75f, 1);
             }
 
-        } else if (currentDragon != dragon &&  !hasDoneSplit) {
+        } else if (currentDragon != dragon && !hasDoneSplit) {
+            Debug.sendDebugMessage(Text.literal("comparing: " + currentDragon.name() + " and " +  dragon.name()));
             currentDragon = Dragon.getPrio(dragon, currentDragon);
             hasDoneSplit = true;
         }
diff --git a/src/main/java/blade/addon/features/dungeon/f7/dragons/Dragon.java b/src/main/java/blade/addon/features/dungeon/f7/dragons/Dragon.java
index b2803a9..dcf7346 100644
--- a/src/main/java/blade/addon/features/dungeon/f7/dragons/Dragon.java
+++ b/src/main/java/blade/addon/features/dungeon/f7/dragons/Dragon.java
@@ -2,7 +2,7 @@ package blade.addon.features.dungeon.f7.dragons;
 
 import blade.addon.utils.dungeon.DungeonClass;
 
-public     enum Dragon {
+public enum Dragon {
     PURPLE(0xffff55ff, 0, 4),
     BLUE(0xff55ffff, 1, 3),
     RED(0xffff5555, 2, 2),
@@ -43,7 +43,16 @@ public     enum Dragon {
     }
 
     public static Dragon getPrio(Dragon dragon1, Dragon dragon2) {
+
         if (DungeonClass.isArchTeam()) {
+            if (DungeonClass.isClass(DungeonClass.HEALER)) {
+                if (dragon1 == Dragon.PURPLE) {
+                    return dragon2;
+                } else if (dragon2 == Dragon.PURPLE) {
+                    return dragon1;
+                }
+            }
+
             if (dragon1.archPrio < dragon2.archPrio) {
                 return dragon1;
             } else {
diff --git a/src/main/java/blade/addon/features/dungeon/f7/invincibility/MaskHighlight.java b/src/main/java/blade/addon/features/dungeon/f7/invincibility/MaskHighlight.java
index 93d37c3..aa0f970 100644
--- a/src/main/java/blade/addon/features/dungeon/f7/invincibility/MaskHighlight.java
+++ b/src/main/java/blade/addon/features/dungeon/f7/invincibility/MaskHighlight.java
@@ -1,6 +1,8 @@
 package blade.addon.features.dungeon.f7.invincibility;
 
+import blade.addon.utils.config.values.Dungeons;
 import blade.addon.utils.data.ItemUtil;
+import blade.addon.utils.rendering.DrawEvents;
 import net.minecraft.client.gui.DrawContext;
 import net.minecraft.item.ItemStack;
 
@@ -8,7 +10,13 @@ public class MaskHighlight {
 
     private static final int COLOR = 0xbb666666;
 
-    public static void draw(DrawContext context, ItemStack stack, int x, int y) {
+    public static void init() {
+        DrawEvents.INVENTORY_SLOT_AFTER.register(MaskHighlight::draw);
+    }
+
+    private static void draw(DrawContext context, ItemStack stack, int x, int y) {
+        if (!Dungeons.maskHighlight) return;
+
         MaskHolder holder = (MaskHolder) (Object) stack;
         assert holder != null;
 
diff --git a/src/main/java/blade/addon/features/dungeon/f7/terms/GoldorTickTimer.java b/src/main/java/blade/addon/features/dungeon/f7/terms/GoldorTickTimer.java
index cd71430..67217ca 100644
--- a/src/main/java/blade/addon/features/dungeon/f7/terms/GoldorTickTimer.java
+++ b/src/main/java/blade/addon/features/dungeon/f7/terms/GoldorTickTimer.java
@@ -28,15 +28,19 @@ public class GoldorTickTimer {
     }
 
     public static boolean display() {
-        return  Floor7.enableGoldorTickTimer && Location.inDungeon() && Phase.inTerminals();
+        return Floor7.enableGoldorTickTimer && Location.inDungeon() && Phase.inTerminals();
     }
 
     public static void render(HUDComponent component, DrawContext context) {
         double num = tick * Constants.TICK_DURATION;
         double mod = num % 3;
-        int color = (mod < 1? Constants.GREEN: mod < 2? Constants.ORANGE: Constants.RED);
+        if (Floor7.inDeathTicks && !Floor7.makeGoldorTickUp) mod = 3.0 - mod;
         if (Floor7.inDeathTicks) num = mod;
 
+        System.out.println(num);
+
+        int color = (mod < 1 ? Constants.GREEN : mod < 2 ? Constants.ORANGE : Constants.RED);
+
         RenderUtils.drawTimer(component, context, num, color);
     }
 }
diff --git a/src/main/java/blade/addon/features/filter/FilterList.java b/src/main/java/blade/addon/features/filter/FilterList.java
index 5a187aa..d2baab4 100644
--- a/src/main/java/blade/addon/features/filter/FilterList.java
+++ b/src/main/java/blade/addon/features/filter/FilterList.java
@@ -9,6 +9,7 @@ import net.minecraft.client.gui.screen.Screen;
 import net.minecraft.client.gui.widget.ButtonWidget;
 import net.minecraft.client.util.Window;
 import net.minecraft.text.Text;
+import net.minecraft.util.Pair;
 import org.joml.Matrix3x2fStack;
 
 public class FilterList extends Screen {
@@ -18,6 +19,10 @@ public class FilterList extends Screen {
     private static final int TITLE_Y_OFFSET = 20;
 
     private static final int BUTTON_HEIGHT = 30;
+    private static final int BUTTON_WIDTH = 100;
+
+    private static final Text INFO_TEXT = Text.literal("To add a filter, input a regex which would match the word fully. I'd highly recommend using regex101.com to create them.");
+
 
     private final Screen parent;
     private final ConfigScroll scroll;
@@ -30,8 +35,9 @@ public class FilterList extends Screen {
         parent = client.currentScreen;
         Window window = client.getWindow();
 
-        addFilter = ButtonWidget.builder(Text.literal("Add Filter"), this::addFilter).position((window.getScaledWidth() - Constants.WIDGET_WIDTH) / 2,  TITLE_Y_OFFSET + 16).width(100).build();
-        scroll = new ConfigScroll(0,  BUTTON_HEIGHT + TITLE_Y_OFFSET + 16, window.getScaledWidth(), window.getScaledHeight() - BUTTON_HEIGHT, Constants.WIDGET_WIDTH);
+        Pair<Integer, Integer> pos = getButtonPos();
+        addFilter = ButtonWidget.builder(Text.literal("Add Filter"), this::addFilter).position(pos.getLeft(), pos.getRight()).width(BUTTON_WIDTH).build();
+        scroll = new ConfigScroll(0, BUTTON_HEIGHT + TITLE_Y_OFFSET + 16, window.getScaledWidth(), window.getScaledHeight() - BUTTON_HEIGHT, Constants.WIDGET_WIDTH);
     }
 
     @Override
@@ -45,6 +51,9 @@ public class FilterList extends Screen {
         stack.scale(TITLE_SCALAR, TITLE_SCALAR);
         context.drawText(this.textRenderer, this.title, 0, 0, TITLE_COLOR, true);
         stack.popMatrix();
+
+        Pair<Integer, Integer> pos = getButtonPos();
+        context.drawWrappedText(this.textRenderer, INFO_TEXT, pos.getLeft() + BUTTON_WIDTH + 5, pos.getRight(), 245, 0xffffffff, true);
     }
 
     @Override
@@ -55,6 +64,13 @@ public class FilterList extends Screen {
         updateList();
     }
 
+    private Pair<Integer, Integer> getButtonPos() {
+        MinecraftClient client = MinecraftClient.getInstance();
+        Window window = client.getWindow();
+        return new Pair<>((window.getScaledWidth() - Constants.WIDGET_WIDTH) / 2, TITLE_Y_OFFSET + 16);
+    }
+
+
     private void updateList() {
         scroll.children().clear();
 
diff --git a/src/main/java/blade/addon/features/item/ItemRarityHighlight.java b/src/main/java/blade/addon/features/item/ItemRarityHighlight.java
index 044fb48..1d1bfe5 100644
--- a/src/main/java/blade/addon/features/item/ItemRarityHighlight.java
+++ b/src/main/java/blade/addon/features/item/ItemRarityHighlight.java
@@ -1,5 +1,7 @@
 package blade.addon.features.item;
 
+import blade.addon.utils.config.values.Visual;
+import blade.addon.utils.rendering.DrawEvents;
 import net.minecraft.client.gui.DrawContext;
 import net.minecraft.component.DataComponentTypes;
 import net.minecraft.component.type.LoreComponent;
@@ -10,7 +12,13 @@ import java.util.List;
 
 public class ItemRarityHighlight {
 
-    public static void draw(DrawContext context, ItemStack stack, int x, int y) {
+    public static void init() {
+        DrawEvents.INVENTORY_SLOT_BEFORE.register(ItemRarityHighlight::draw);
+        DrawEvents.HUD_SLOT_BEFORE.register(ItemRarityHighlight::draw);
+    }
+
+    private static void draw(DrawContext context, ItemStack stack, int x, int y) {
+        if (!Visual.itemRarityBackground) return;
         ItemRarityHolder holder = (ItemRarityHolder) (Object) stack;
         assert holder != null;
 
@@ -38,8 +46,7 @@ public class ItemRarityHighlight {
 
             for (String testString: rarityStrings) {
                 try {
-                    ItemRarity rarity = ItemRarity.valueOf(testString);
-                    return rarity;
+                    return ItemRarity.valueOf(testString);
                 } catch (IllegalArgumentException ignored) {
 
                 }
diff --git a/src/main/java/blade/addon/features/item/ProtectItem.java b/src/main/java/blade/addon/features/item/ProtectItem.java
index 594599f..10db8f5 100644
--- a/src/main/java/blade/addon/features/item/ProtectItem.java
+++ b/src/main/java/blade/addon/features/item/ProtectItem.java
@@ -1,12 +1,14 @@
 package blade.addon.features.item;
 
 import blade.addon.utils.Constants;
+import blade.addon.utils.config.values.Visual;
 import blade.addon.utils.debug.Debug;
 import blade.addon.utils.Location;
 import blade.addon.utils.Misc;
 import blade.addon.utils.config.FolderUtility;
 import blade.addon.utils.data.ItemUtil;
 import blade.addon.utils.dungeon.Phase;
+import blade.addon.utils.rendering.DrawEvents;
 import config.practical.manager.ConfigManager;
 import config.practical.manager.ConfigValue;
 import net.minecraft.client.MinecraftClient;
@@ -34,6 +36,11 @@ public class ProtectItem {
     @ConfigValue
     public static HashSet<String> protectedItems = new HashSet<>();
 
+    public static void init() {
+        DrawEvents.INVENTORY_SLOT_BEFORE.register(ProtectItem::draw);
+        DrawEvents.HUD_SLOT_BEFORE.register(ProtectItem::draw);
+    }
+
     public static void protectSelected() {
         ClientPlayerEntity player = MinecraftClient.getInstance().player;
         if (player == null) {
@@ -73,7 +80,8 @@ public class ProtectItem {
         return protectedItems.contains(uuid);
     }
 
-    public static void draw(DrawContext context, ItemStack stack, int x, int y) {
+    private static void draw(DrawContext context, ItemStack stack, int x, int y) {
+        if (!Visual.highlightProtectedItem) return;
         ProtectedItemHolder holder = (ProtectedItemHolder) (Object) stack;
         assert holder != null;
 
@@ -131,6 +139,6 @@ public class ProtectItem {
     }
 
     public static boolean isInADungeon() {
-        return Location.inDungeon() && Phase.runStarted() && !Phase.runOver() && Location.hasRecivedLocation();
+        return Location.inDungeon() && Phase.runStarted() && !Phase.runOver() && Location.hasReceivedLocation();
     }
 }
diff --git a/src/main/java/blade/addon/features/item/SelectedPetHighlight.java b/src/main/java/blade/addon/features/item/SelectedPetHighlight.java
index c00db1b..58d6602 100644
--- a/src/main/java/blade/addon/features/item/SelectedPetHighlight.java
+++ b/src/main/java/blade/addon/features/item/SelectedPetHighlight.java
@@ -1,5 +1,7 @@
 package blade.addon.features.item;
 
+import blade.addon.utils.config.values.ExtraOptions;
+import blade.addon.utils.rendering.DrawEvents;
 import net.minecraft.client.gui.DrawContext;
 import net.minecraft.component.DataComponentTypes;
 import net.minecraft.component.type.LoreComponent;
@@ -10,7 +12,11 @@ import java.util.List;
 
 public class SelectedPetHighlight {
 
-    public static void draw(DrawContext context, ItemStack stack, int x, int y) {
+    public static void init() {
+        DrawEvents.INVENTORY_SLOT_BEFORE.register(SelectedPetHighlight::draw);
+    }
+
+    private static void draw(DrawContext context, ItemStack stack, int x, int y) {
         PetHolder holder = (PetHolder) (Object) stack;
         assert holder != null;
 
@@ -20,17 +26,17 @@ public class SelectedPetHighlight {
 
         if (!holder.blade_addons$isSelected()) return;
 
-        context.fill(x, y, x + 16, y + 16, 0xffff0000);
+        context.fill(x, y, x + 16, y + 16, ExtraOptions.petHighlightColor);
     }
 
     public static boolean isSelected(ItemStack item) {
         LoreComponent lore = item.get(DataComponentTypes.LORE);
-        if (lore == null)  return false;
+        if (lore == null) return false;
 
         List<Text> lines = lore.lines();
         if (lines.isEmpty()) return false;
 
-        for(Text text: lines) {
+        for (Text text : lines) {
             if (text == null) continue;
             if (text.getString().equals("Click to despawn!")) return true;
         }
diff --git a/src/main/java/blade/addon/features/item/StarCountHighlight.java b/src/main/java/blade/addon/features/item/StarCountHighlight.java
index a9c6a4a..d3daad4 100644
--- a/src/main/java/blade/addon/features/item/StarCountHighlight.java
+++ b/src/main/java/blade/addon/features/item/StarCountHighlight.java
@@ -1,5 +1,7 @@
 package blade.addon.features.item;
 
+import blade.addon.utils.config.values.Visual;
+import blade.addon.utils.rendering.DrawEvents;
 import net.minecraft.client.MinecraftClient;
 import net.minecraft.client.font.TextRenderer;
 import net.minecraft.client.gui.DrawContext;
@@ -10,7 +12,15 @@ import net.minecraft.nbt.NbtCompound;
 
 public class StarCountHighlight {
 
-    public static void draw(DrawContext context, ItemStack stack, int x, int y) {
+    public static void init() {
+        DrawEvents.INVENTORY_SLOT_AFTER.register(StarCountHighlight::draw);
+        DrawEvents.HUD_SLOT_AFTER.register(StarCountHighlight::draw);
+    }
+
+    private static void draw(DrawContext context, ItemStack stack, int x, int y) {
+
+        if (!Visual.drawStarCount) return;
+
         StarCountHolder holder = (StarCountHolder) (Object) stack;
         assert holder != null;
 
diff --git a/src/main/java/blade/addon/features/notifications/Notifications.java b/src/main/java/blade/addon/features/notifications/Notifications.java
index 8bcf74e..2f65f39 100644
--- a/src/main/java/blade/addon/features/notifications/Notifications.java
+++ b/src/main/java/blade/addon/features/notifications/Notifications.java
@@ -1,6 +1,8 @@
 package blade.addon.features.notifications;
 
+import blade.addon.utils.Misc;
 import blade.addon.utils.config.FolderUtility;
+import blade.addon.utils.debug.Debug;
 import blade.addon.utils.events.Events;
 import blade.addon.utils.rendering.RenderUtils;
 import com.google.gson.Gson;
@@ -38,6 +40,10 @@ public class Notifications {
         load();
         Events.ON_GAME_MESSAGE.register(text -> {
             String message = text.getString();
+            if (Debug.sendNotiDebug) {
+                Misc.addChatMessage(Text.literal(message.replaceAll("§", "&")));
+            }
+
             for (Notification notification : notifications) {
                 notification.testMessage(message);
             }
diff --git a/src/main/java/blade/addon/features/other/SearchBar.java b/src/main/java/blade/addon/features/other/SearchBar.java
new file mode 100644
index 0000000..e9d7821
--- /dev/null
+++ b/src/main/java/blade/addon/features/other/SearchBar.java
@@ -0,0 +1,94 @@
+package blade.addon.features.other;
+
+import blade.addon.utils.config.values.ExtraOptions;
+import blade.addon.utils.rendering.DrawEvents;
+import net.minecraft.client.MinecraftClient;
+import net.minecraft.client.font.TextRenderer;
+import net.minecraft.client.gui.Click;
+import net.minecraft.client.gui.DrawContext;
+import net.minecraft.client.gui.widget.TextFieldWidget;
+import net.minecraft.client.input.CharInput;
+import net.minecraft.client.input.KeyInput;
+import net.minecraft.client.util.Window;
+import net.minecraft.text.Text;
+import org.lwjgl.glfw.GLFW;
+
+public class SearchBar {
+
+    private static final int SEARCH_Y = 20;
+    private static final int SEARCH_WIDTH = 150;
+    private static final int SEARCH_HEIGHT = 20;
+    private static TextFieldWidget searchBar;
+    private static boolean shouldDisplay = false;
+    private static String searchTerm = "";
+
+    public static void init() {
+        DrawEvents.INVENTORY_SLOT_AFTER.register((context, item, x, y) -> {
+            if (shouldDisplay() && !searchTerm.isEmpty() && ExtraOptions.toggleableSearchBar) {
+                String name = item.getName().getString();
+                if (!name.contains(searchTerm) || name.equals("Air")) {
+                    context.fill(x, y, x + 16, y + 16, 0xaa111111);
+                }
+            }
+        });
+    }
+
+    public static void render(DrawContext context, int mouseX, int mouseY, float deltaTicks) {
+        if (!exists() || !shouldDisplay() || !ExtraOptions.toggleableSearchBar) return;
+        searchBar.render(context, mouseX, mouseY, deltaTicks);
+    }
+
+    public static boolean keyPressed(KeyInput input) {
+        if (!exists() || !ExtraOptions.toggleableSearchBar) return false;
+
+        boolean ctrlIsPressed = (input.modifiers() & GLFW.GLFW_MOD_CONTROL) != 0;
+
+        if (ctrlIsPressed && input.key() == GLFW.GLFW_KEY_F) {
+            shouldDisplay = !shouldDisplay;
+            return true;
+        } else if (shouldDisplay() && searchBar.isFocused() && input.key() != GLFW.GLFW_KEY_ESCAPE) {
+            searchBar.keyPressed(input);
+            return true;
+        }
+        return false;
+    }
+
+    public static void CharTyped(CharInput input) {
+        if (!exists() || !searchBar.isFocused() || !shouldDisplay() || !ExtraOptions.toggleableSearchBar) return;
+        searchBar.charTyped(input);
+    }
+
+    public static void onMouseClick(Click click) {
+        if (!exists() || !shouldDisplay() || !ExtraOptions.toggleableSearchBar) return;
+        searchBar.setFocused(inBounds(click.x(), click.y()));
+    }
+
+
+    public static boolean shouldDisplay() {
+        return shouldDisplay;
+    }
+
+    private static boolean exists() {
+        if (searchBar != null) return true;
+
+        MinecraftClient mc = MinecraftClient.getInstance();
+        TextRenderer textRenderer = mc.textRenderer;
+        Window window = mc.getWindow();
+
+        if (window == null || textRenderer == null) return false;
+
+        searchBar = new TextFieldWidget(textRenderer, (window.getScaledWidth() - SEARCH_WIDTH) / 2, SEARCH_Y, SEARCH_WIDTH, SEARCH_HEIGHT, Text.literal(""));
+        searchBar.setChangedListener(string -> searchTerm = string);
+        return true;
+    }
+
+    private static boolean inBounds(double x, double y) {
+        int sx = searchBar.getX();
+        int sy = searchBar.getY();
+        int sw = searchBar.getWidth();
+        int sh = searchBar.getHeight();
+
+        return x >= sx && x <= sx + sw && y >= sy && y <= sy + sh;
+    }
+
+}
diff --git a/src/main/java/blade/addon/features/other/SelectedPet.java b/src/main/java/blade/addon/features/other/SelectedPet.java
index 9f6a5d3..7ec28a9 100644
--- a/src/main/java/blade/addon/features/other/SelectedPet.java
+++ b/src/main/java/blade/addon/features/other/SelectedPet.java
@@ -13,6 +13,7 @@ import net.minecraft.client.MinecraftClient;
 import net.minecraft.client.font.TextRenderer;
 import net.minecraft.client.gl.RenderPipelines;
 import net.minecraft.client.gui.DrawContext;
+import net.minecraft.text.MutableText;
 import net.minecraft.text.Style;
 import net.minecraft.text.Text;
 import net.minecraft.util.Identifier;
@@ -28,13 +29,15 @@ public class SelectedPet {
     private static final Pattern TAB_PET_PATTERN = Pattern.compile(" \\[Lvl \\d+] (?:\\[[^\\]]+\\] )?(.+)");
     private static final Pattern MANUAL_EQUIP_PET_PATTERN = Pattern.compile("^You (summoned|despawned) your (?:\\[Lvl \\d+] )?(?:\\[[^\\]]+\\] )?(.+)!$");
     private static final Pattern RULE_EQUIP_PET_PATTERN = Pattern.compile("^Autopet equipped your \\[Lvl \\d+] (?:\\[[^\\]]+\\] )?(.+)! VIEW RULE$");
+    private static final Pattern LEVEL_REGEX = Pattern.compile("\\[Lvl (\\d+)]");
 
     private static final int TOTAL_TICKS = 20;
 
-    private static final Text NO_PET = Text.literal("§cNo pet");
+    private static final MutableText NO_PET = Text.literal("§cNo pet");
     private static final String identifierPrefix = "pets/";
 
-    private static Text currentPetText = NO_PET;
+    private static MutableText currentPetText = NO_PET;
+    private static int currentPetLevel = -1;
     private static String currentPetString = "";
     private static Identifier spriteId = null;
     private static int tick = 0;
@@ -52,11 +55,12 @@ public class SelectedPet {
 
                 String name = matcher.group(2).replace(" ✦", "");
                 Style style = getStyle(message, name);
-                summonPet(name, Text.literal(name).setStyle(style), false);
+                summonPet(name, Text.literal(name).setStyle(style), getLevel(string), false);
                 return;
             }
 
-            matcher = RULE_EQUIP_PET_PATTERN.matcher(string.replaceAll("§.", ""));
+            String unformattedLine = string.replaceAll("§.", "");
+            matcher = RULE_EQUIP_PET_PATTERN.matcher(unformattedLine);
             if (matcher.find()) {
                 String name = matcher.group(1).replace(" ✦", "");
 
@@ -66,7 +70,7 @@ public class SelectedPet {
                     textName = string.substring(index - COLOR_OFFSET, index) + textName;
                 }
 
-                summonPet(name, Text.literal(textName), true);
+                summonPet(name, Text.literal(textName), getLevel(unformattedLine), true);
             }
         });
 
@@ -85,7 +89,7 @@ public class SelectedPet {
             if (matcher.find()) {
                 String petName = matcher.group(1).replace(" ✦", "");
                 Style style = getStyle(text, petName);
-                summonPet(petName, Text.literal(petName).setStyle(style), false);
+                summonPet(petName, Text.literal(petName).setStyle(style), getLevel(string), false);
             }
 
             return false;
@@ -100,12 +104,14 @@ public class SelectedPet {
     private static void despawnPet() {
         Events.ON_PET.invoke(petEvent -> petEvent.onPet(""));
         currentPetText = NO_PET;
+        currentPetLevel = -1;
         currentPetString = "";
         spriteId = null;
 
     }
 
-    private static void summonPet(String stringName, Text textName, boolean sendSound) {
+    private static void summonPet(String stringName, MutableText textName, int level, boolean sendSound) {
+        currentPetLevel = level;
         if (stringName.equals(currentPetString)) {
             return;
         }
@@ -135,6 +141,15 @@ public class SelectedPet {
         return style;
     }
 
+    private static int getLevel(String line) {
+        Matcher matcher = LEVEL_REGEX.matcher(line);
+        if (matcher.find()) {
+            return Integer.parseInt(matcher.group(1));
+        }
+
+        return -1;
+    }
+
     private static void updateSprite(String petName) {
         if (petName == null) {
             spriteId = null;
@@ -162,7 +177,15 @@ public class SelectedPet {
 
         TextRenderer textRenderer = MinecraftClient.getInstance().textRenderer;
 
-        context.drawText(textRenderer, currentPetText, x + (ExtraOptions.includePetSprite ? 18 : 0), y + component.getHeight() - textRenderer.fontHeight, 0xffffffff, true);
+        int textX = x + (ExtraOptions.includePetSprite ? 18 : 0);
+        int textY = y + component.getHeight() - textRenderer.fontHeight;
+
+        if (ExtraOptions.displayPetLevel && currentPetText != NO_PET) {
+            context.drawText(textRenderer, Text.literal("§7[Lvl " + (currentPetLevel != -1 ? currentPetLevel : "???") + "]"), textX, textY, 0xffffffff, true);
+            textY -= textRenderer.fontHeight;
+        }
+
+        context.drawText(textRenderer, currentPetText, textX, textY, 0xffffffff, true);
     }
 
     public static boolean displayNotification() {
diff --git a/src/main/java/blade/addon/features/other/StunWaypoint.java b/src/main/java/blade/addon/features/other/StunWaypoint.java
new file mode 100644
index 0000000..742152b
--- /dev/null
+++ b/src/main/java/blade/addon/features/other/StunWaypoint.java
@@ -0,0 +1,62 @@
+package blade.addon.features.other;
+
+import blade.addon.utils.Location;
+import blade.addon.utils.config.values.ExtraOptions;
+import blade.addon.utils.events.Events;
+import blade.addon.utils.rendering.RenderUtils;
+import blade.addon.utils.rendering.RenderingEvents;
+import net.minecraft.network.packet.s2c.play.PlayerPositionLookS2CPacket;
+import net.minecraft.util.math.Box;
+import net.minecraft.util.math.Vec3d;
+
+public class StunWaypoint {
+
+    private static final Box BLOCK = new Box(-168, 27, -169, -167, 28, -168);
+
+    private static boolean inBelly = false;
+    private static boolean inP3 = false;
+
+    public static void init() {
+
+        Events.ON_GAME_MESSAGE.register(text -> {
+            if (!Location.in(Location.KUUDRA) || !ExtraOptions.stunWaypoint) return false;
+            String string = text.getString();
+
+            if (string.equals("[NPC] Elle: Phew! The Ballista is finally ready! It should be strong enough to tank Kuudra's blows now!")) {
+                inP3 = true;
+                return false;
+            }
+
+            if (string.equals("[NPC] Elle: POW! SURELY THAT'S IT! I don't think he has any more in him!")) {
+                reset();
+                return false;
+            }
+
+            return false;
+        });
+
+        Events.ON_PACKET.register(packet -> {
+            if (!Location.in(Location.KUUDRA) || !ExtraOptions.stunWaypoint) return false;
+
+            if (packet instanceof PlayerPositionLookS2CPacket posPacket) {
+                Vec3d pos = posPacket.change().position();
+                if (pos.x == -161 && pos.y == 49 && pos.z == -186) {
+                    inBelly = true;
+                }
+
+            }
+            return false;
+        });
+
+        RenderingEvents.NO_DEPTH_OUTLINE_ENTITY.register((context, matrixStack, consumer) -> {
+            if (!inP3 || !inBelly || !Location.in(Location.KUUDRA) || !ExtraOptions.stunWaypoint) return;
+            RenderUtils.renderOutline(matrixStack, consumer, BLOCK, new float[]{0, 1, 1, 1});
+        });
+    }
+
+
+    private static void reset() {
+        inBelly = false;
+        inP3 = false;
+    }
+}
diff --git a/src/main/java/blade/addon/mixin/ClientConnectionMixin.java b/src/main/java/blade/addon/mixin/ClientConnectionMixin.java
index efbca60..e9a6e29 100644
--- a/src/main/java/blade/addon/mixin/ClientConnectionMixin.java
+++ b/src/main/java/blade/addon/mixin/ClientConnectionMixin.java
@@ -8,6 +8,7 @@ import net.minecraft.network.ClientConnection;
 import net.minecraft.network.packet.Packet;
 import net.minecraft.network.packet.s2c.common.CommonPingS2CPacket;
 import net.minecraft.network.packet.s2c.play.GameMessageS2CPacket;
+import net.minecraft.text.Text;
 import org.spongepowered.asm.mixin.Mixin;
 import org.spongepowered.asm.mixin.injection.At;
 import org.spongepowered.asm.mixin.injection.Inject;
@@ -16,7 +17,7 @@ import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
 @Mixin(ClientConnection.class)
 public class ClientConnectionMixin {
 
-    @Inject(method = "channelRead0(Lio/netty/channel/ChannelHandlerContext;Lnet/minecraft/network/packet/Packet;)V", at = @At("HEAD"), order = 0, cancellable = true)
+    @Inject(method = "channelRead0(Lio/netty/channel/ChannelHandlerContext;Lnet/minecraft/network/packet/Packet;)V", at = @At(value = "INVOKE", target = "Lnet/minecraft/network/ClientConnection;handlePacket(Lnet/minecraft/network/packet/Packet;Lnet/minecraft/network/listener/PacketListener;)V"), order = 0, cancellable = true)
     private void channelRead0(ChannelHandlerContext channelHandlerContext, Packet<?> packet, CallbackInfo ci) {
         if (packet instanceof CommonPingS2CPacket common) {
             //admins send these packets too for inventory changes
@@ -25,14 +26,13 @@ public class ClientConnectionMixin {
             Events.ON_SERVER_TICK.invoke(ServerTickEvent::onServerTick);
         }
 
-        if (packet instanceof GameMessageS2CPacket message) {
-            if (Events.ON_GAME_MESSAGE.invoke(gameMessageEvent -> gameMessageEvent.onGameMessage(message.content()))) {
-                ci.cancel();
-                return;
+        if (packet instanceof GameMessageS2CPacket(Text content, boolean overlay) && !overlay) {
+            if (Events.ON_GAME_MESSAGE.invoke(gameMessageEvent -> gameMessageEvent.onGameMessage(content))) {
+               ci.cancel();
             }
         }
 
-        Events.ON_PACKET.invoke(packetEvent ->  packetEvent.onPacket(packet));
+        Events.ON_PACKET.invoke(packetEvent -> packetEvent.onPacket(packet));
     }
 
     @Inject(method = "sendImmediately", at = @At("HEAD"))
diff --git a/src/main/java/blade/addon/mixin/ClientPlayNetworkHandlerMixin.java b/src/main/java/blade/addon/mixin/ClientPlayNetworkHandlerMixin.java
index 0100f57..7009d8b 100644
--- a/src/main/java/blade/addon/mixin/ClientPlayNetworkHandlerMixin.java
+++ b/src/main/java/blade/addon/mixin/ClientPlayNetworkHandlerMixin.java
@@ -6,13 +6,19 @@ import blade.addon.utils.config.values.ExtraOptions;
 import blade.addon.utils.debug.Debug;
 import blade.addon.utils.events.Events;
 import com.llamalad7.mixinextras.injector.ModifyExpressionValue;
+import com.llamalad7.mixinextras.injector.wrapoperation.Operation;
+import com.llamalad7.mixinextras.injector.wrapoperation.WrapOperation;
 import com.llamalad7.mixinextras.sugar.Local;
 import net.minecraft.client.network.ClientPlayNetworkHandler;
 import net.minecraft.client.network.PlayerListEntry;
 import net.minecraft.client.world.ClientWorld;
 import net.minecraft.entity.Entity;
+import net.minecraft.network.listener.PacketListener;
+import net.minecraft.network.packet.Packet;
 import net.minecraft.network.packet.s2c.play.EntitySpawnS2CPacket;
 import net.minecraft.network.packet.s2c.play.EntityTrackerUpdateS2CPacket;
+import net.minecraft.network.packet.s2c.play.GameMessageS2CPacket;
+import net.minecraft.network.packet.s2c.play.ParticleS2CPacket;
 import net.minecraft.network.packet.s2c.play.PlaySoundFromEntityS2CPacket;
 import net.minecraft.network.packet.s2c.play.PlaySoundS2CPacket;
 import net.minecraft.network.packet.s2c.play.PlayerListS2CPacket;
@@ -104,4 +110,20 @@ public class ClientPlayNetworkHandlerMixin {
         Events.ON_ENTITY_SPAWNED.invoke(entityTrackEvent -> entityTrackEvent.onEntity(entity, world));
     }
 
+    @Inject(method = "onParticle", at = @At("HEAD"))
+    private void onParticle(ParticleS2CPacket packet, CallbackInfo ci) {
+        Events.ON_PARTICLE.invoke(particleEvent -> particleEvent.onParticle(packet));
+    }
+
+    @WrapOperation(method = "onBundle", at = @At(value = "INVOKE", target = "Lnet/minecraft/network/packet/Packet;apply(Lnet/minecraft/network/listener/PacketListener;)V"))
+    private void apply(Packet<?> packet, PacketListener listener, Operation<Void> original) {
+        if (packet instanceof GameMessageS2CPacket(Text content, boolean overlay) && !overlay) {
+            if (Events.ON_GAME_MESSAGE.invoke(gameMessageEvent -> gameMessageEvent.onGameMessage(content))) {
+                return;
+            }
+        }
+
+        Events.ON_PACKET.invoke(packetEvent -> packetEvent.onPacket(packet));
+        original.call(packet, listener);
+    }
 }
\ No newline at end of file
diff --git a/src/main/java/blade/addon/mixin/EntityRenderManagerMixin.java b/src/main/java/blade/addon/mixin/EntityRenderManagerMixin.java
index 99f1f96..5b32699 100644
--- a/src/main/java/blade/addon/mixin/EntityRenderManagerMixin.java
+++ b/src/main/java/blade/addon/mixin/EntityRenderManagerMixin.java
@@ -14,6 +14,8 @@ import net.minecraft.entity.Entity;
 import net.minecraft.entity.ItemEntity;
 import net.minecraft.entity.passive.SheepEntity;
 import net.minecraft.entity.player.PlayerEntity;
+import net.minecraft.entity.projectile.FishingBobberEntity;
+import net.minecraft.util.math.Vec3d;
 import org.spongepowered.asm.mixin.Mixin;
 import org.spongepowered.asm.mixin.injection.At;
 import org.spongepowered.asm.mixin.injection.Inject;
@@ -49,5 +51,18 @@ public class EntityRenderManagerMixin {
         if (entity instanceof SheepEntity) {
             if (Location.inDungeon() && MobHighlight.hideSheep) cir.setReturnValue(false);
         }
+
+        if (Visual.oldFishingRod && entity instanceof FishingBobberEntity bobber) {
+            ClientPlayerEntity clientPlayer = MinecraftClient.getInstance().player;
+            if (clientPlayer == null || bobber.getOwner() != clientPlayer) return;
+
+            //Pretty much just hides the bobber if it's too close
+            //its def not exact, but it's good enough for now
+            Vec3d pos = bobber.getInterpolator().getLerpedPos();
+            if (clientPlayer.getEntityPos().distanceTo(pos) < 2 && bobber.age < 6 && pos.y > clientPlayer.getY() + 1.2 && clientPlayer.getPitch() > -60) {
+                cir.setReturnValue(false);
+            }
+        }
+
     }
 }
diff --git a/src/main/java/blade/addon/mixin/HandledScreenMixin.java b/src/main/java/blade/addon/mixin/HandledScreenMixin.java
index 939bd03..1b05bca 100644
--- a/src/main/java/blade/addon/mixin/HandledScreenMixin.java
+++ b/src/main/java/blade/addon/mixin/HandledScreenMixin.java
@@ -1,16 +1,13 @@
 package blade.addon.mixin;
 
-import blade.addon.features.dungeon.f7.invincibility.MaskHighlight;
-import blade.addon.features.item.ItemRarityHighlight;
 import blade.addon.features.item.ProtectItem;
-import blade.addon.features.item.SelectedPetHighlight;
-import blade.addon.features.item.StarCountHighlight;
-import blade.addon.utils.config.values.Dungeons;
-import blade.addon.utils.config.values.ExtraOptions;
-import blade.addon.utils.config.values.Visual;
+import blade.addon.features.other.SearchBar;
+import blade.addon.utils.rendering.DrawEvents;
+import net.minecraft.client.gui.Click;
 import net.minecraft.client.gui.DrawContext;
-import net.minecraft.client.gui.screen.ingame.GenericContainerScreen;
+import net.minecraft.client.gui.screen.Screen;
 import net.minecraft.client.gui.screen.ingame.HandledScreen;
+import net.minecraft.client.input.KeyInput;
 import net.minecraft.item.ItemStack;
 import net.minecraft.screen.ScreenHandler;
 import net.minecraft.screen.slot.Slot;
@@ -23,9 +20,10 @@ 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.callback.CallbackInfo;
+import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
 
 @Mixin(HandledScreen.class)
-public class HandledScreenMixin<T extends ScreenHandler> {
+public abstract class HandledScreenMixin<T extends ScreenHandler> extends Screen {
 
     @Shadow
     @Final
@@ -33,32 +31,21 @@ public class HandledScreenMixin<T extends ScreenHandler> {
     @Unique
     private static final int INVALID_SLOT_ID = -999;
 
+    protected HandledScreenMixin(Text title) {
+        super(title);
+    }
+
+    @Inject(method = "render", at=@At("TAIL"))
+    private static void render(DrawContext context, int mouseX, int mouseY, float deltaTicks, CallbackInfo ci) {
+        SearchBar.render(context, mouseX, mouseY, deltaTicks);
+    }
+
     @Inject(method = "drawSlot", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/gui/DrawContext;drawItem(Lnet/minecraft/item/ItemStack;III)V"))
     public void drawBackground(DrawContext context, Slot slot, CallbackInfo ci) {
-
         int x = slot.x;
         int y = slot.y;
         ItemStack stack = slot.getStack();
-
-        if (ExtraOptions.highlightSelectedPet) {
-            HandledScreen screen = (HandledScreen) (Object) this;
-            if (screen instanceof GenericContainerScreen containerScreen) {
-                Text title = containerScreen.getTitle();
-                if (title == null) return;
-                if (title.getString().equals("Pets")) {
-                    SelectedPetHighlight.draw(context, stack, x, y);
-                }
-
-            }
-        }
-
-        if (Visual.highlightProtectedItem) {
-            ProtectItem.draw(context, stack, x, y);
-        }
-
-        if (Visual.itemRarityBackground) {
-            ItemRarityHighlight.draw(context, stack, x, y);
-        }
+        DrawEvents.INVENTORY_SLOT_BEFORE.invoke(event -> event.draw(context, stack, x, y));
     }
 
     @Inject(method = "drawSlot", at = @At(value = "TAIL"))
@@ -66,14 +53,17 @@ public class HandledScreenMixin<T extends ScreenHandler> {
         int x = slot.x;
         int y = slot.y;
         ItemStack stack = slot.getStack();
+        DrawEvents.INVENTORY_SLOT_AFTER.invoke(event -> event.draw(context, stack, x, y));
+    }
 
-        if (Dungeons.maskHighlight) {
-            MaskHighlight.draw(context, stack, x, y);
-        }
+    @Inject(method = "keyPressed", at=@At("HEAD"), cancellable = true)
+    private void keyPressed(KeyInput input, CallbackInfoReturnable<Boolean> cir) {
+        if (SearchBar.keyPressed(input)) cir.setReturnValue(false);
+    }
 
-        if (Visual.drawStarCount) {
-            StarCountHighlight.draw(context, stack, x, y);
-        }
+    @Inject(method = "mouseClicked", at=@At("HEAD"))
+    private void onMouseClick(Click click, boolean doubled, CallbackInfoReturnable<Boolean> cir) {
+        SearchBar.onMouseClick(click);
     }
 
     @Inject(method ="onMouseClick(Lnet/minecraft/screen/slot/Slot;IILnet/minecraft/screen/slot/SlotActionType;)V", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/gui/screen/ingame/HandledScreen;onMouseClick(Lnet/minecraft/screen/slot/Slot;Lnet/minecraft/screen/slot/SlotActionType;)V"), cancellable = true)
diff --git a/src/main/java/blade/addon/mixin/InGameHudMixin.java b/src/main/java/blade/addon/mixin/InGameHudMixin.java
index 2b46667..53d1094 100644
--- a/src/main/java/blade/addon/mixin/InGameHudMixin.java
+++ b/src/main/java/blade/addon/mixin/InGameHudMixin.java
@@ -3,12 +3,10 @@ package blade.addon.mixin;
 import blade.addon.features.dungeon.f7.terms.DeviceNotifier;
 import blade.addon.features.dungeon.f7.terms.TitleHider;
 import blade.addon.features.item.HeldItemToolTip;
-import blade.addon.features.item.ItemRarityHighlight;
-import blade.addon.features.item.ProtectItem;
-import blade.addon.features.item.StarCountHighlight;
 import blade.addon.utils.config.values.ExtraOptions;
 import blade.addon.utils.config.values.Visual;
 import blade.addon.utils.interfaces.GameHud;
+import blade.addon.utils.rendering.DrawEvents;
 import com.llamalad7.mixinextras.sugar.Local;
 import net.minecraft.client.gui.DrawContext;
 import net.minecraft.client.gui.hud.InGameHud;
@@ -46,21 +44,12 @@ public class InGameHudMixin implements GameHud {
 
     @Inject(method = "renderHotbarItem", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/gui/DrawContext;drawItem(Lnet/minecraft/entity/LivingEntity;Lnet/minecraft/item/ItemStack;III)V"), order = 2000)
     public void drawBackground(DrawContext context, int x, int y, RenderTickCounter tickCounter, PlayerEntity player, ItemStack stack, int seed, CallbackInfo ci) {
-        if (Visual.itemRarityBackground) {
-            ItemRarityHighlight.draw(context, stack, x, y);
-        }
-
-        if (Visual.highlightProtectedItem) {
-            ProtectItem.draw(context, stack, x, y);
-        }
-
+        DrawEvents.HUD_SLOT_BEFORE.invoke(slotEvent -> slotEvent.draw(context, stack, x, y));
     }
 
     @Inject(method = "renderHotbarItem", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/gui/DrawContext;drawItem(Lnet/minecraft/entity/LivingEntity;Lnet/minecraft/item/ItemStack;III)V", shift = At.Shift.AFTER), order = 2000)
     public void drawStar(DrawContext context, int x, int y, RenderTickCounter tickCounter, PlayerEntity player, ItemStack stack, int seed, CallbackInfo ci) {
-        if (Visual.drawStarCount) {
-            StarCountHighlight.draw(context, stack, x, y);
-        }
+        DrawEvents.HUD_SLOT_AFTER.invoke(slotEvent -> slotEvent.draw(context, stack, x, y));
     }
 
     @Inject(method = "renderStatusEffectOverlay", at = @At("HEAD"), cancellable = true, order = 2000)
diff --git a/src/main/java/blade/addon/mixin/InventoryScreenMixin.java b/src/main/java/blade/addon/mixin/InventoryScreenMixin.java
index ba9f01d..0ea4ddd 100644
--- a/src/main/java/blade/addon/mixin/InventoryScreenMixin.java
+++ b/src/main/java/blade/addon/mixin/InventoryScreenMixin.java
@@ -1,6 +1,7 @@
 package blade.addon.mixin;
 
 import blade.addon.features.other.InventoryButton;
+import blade.addon.features.other.SearchBar;
 import net.minecraft.client.gui.Click;
 import net.minecraft.client.gui.DrawContext;
 import net.minecraft.client.gui.screen.ingame.InventoryScreen;
@@ -31,6 +32,7 @@ public abstract class InventoryScreenMixin extends RecipeBookScreen<PlayerScreen
         stack.translate(x, y);
         InventoryButton.renderAll(context, mouseX, mouseY, deltaTicks);
         stack.popMatrix();
+        SearchBar.render(context, mouseX, mouseY, deltaTicks);
     }
 
     @Override
diff --git a/src/main/java/blade/addon/mixin/KeyboardMixin.java b/src/main/java/blade/addon/mixin/KeyboardMixin.java
new file mode 100644
index 0000000..7975a7f
--- /dev/null
+++ b/src/main/java/blade/addon/mixin/KeyboardMixin.java
@@ -0,0 +1,26 @@
+package blade.addon.mixin;
+
+import blade.addon.features.other.SearchBar;
+import blade.addon.utils.config.values.ExtraOptions;
+import com.llamalad7.mixinextras.sugar.Local;
+import net.minecraft.client.Keyboard;
+import net.minecraft.client.gui.screen.Screen;
+import net.minecraft.client.gui.screen.ingame.HandledScreen;
+import net.minecraft.client.input.CharInput;
+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;
+
+@Mixin(Keyboard.class)
+public class KeyboardMixin {
+
+    @Inject(method = "onChar", at= @At(value = "INVOKE", target = "Lnet/minecraft/client/gui/screen/Screen;charTyped(Lnet/minecraft/client/input/CharInput;)Z"))
+    private void onChar(long window, CharInput input, CallbackInfo ci, @Local Screen screen) {
+        if (ExtraOptions.toggleableSearchBar && screen instanceof HandledScreen<?>) {
+            SearchBar.CharTyped(input);
+        }
+    }
+
+
+}
diff --git a/src/main/java/blade/addon/mixin/MinecraftClientMixin.java b/src/main/java/blade/addon/mixin/MinecraftClientMixin.java
index 8b582e9..1272b97 100644
--- a/src/main/java/blade/addon/mixin/MinecraftClientMixin.java
+++ b/src/main/java/blade/addon/mixin/MinecraftClientMixin.java
@@ -25,10 +25,10 @@ public class MinecraftClientMixin {
     public HitResult crosshairTarget;
 
     @Inject(method = "doItemUse", at = @At(value = "INVOKE_ASSIGN", target = "Lnet/minecraft/item/ItemStack;getCount()I"), cancellable = true)
-    private void testInteraction(CallbackInfo ci, @Local ItemStack itemStack) {
+    private void testInteractionBlock(CallbackInfo ci, @Local ItemStack itemStack) {
         BlockHitResult blockHitResult = (BlockHitResult) this.crosshairTarget;
         if (blockHitResult == null) return;
-        if (Events.ON_BLOCK_INTERACTION.invoke(blockInteractionEvent -> blockInteractionEvent.iteract(blockHitResult, itemStack))) {
+        if (Events.ON_BLOCK_INTERACTION.invoke(blockInteractionEvent -> blockInteractionEvent.interact(blockHitResult, itemStack))) {
             ci.cancel();
         }
     }
diff --git a/src/main/java/blade/addon/utils/Commands.java b/src/main/java/blade/addon/utils/Commands.java
index 71e9c16..8d17571 100644
--- a/src/main/java/blade/addon/utils/Commands.java
+++ b/src/main/java/blade/addon/utils/Commands.java
@@ -199,6 +199,18 @@ public class Commands {
                 Misc.executeCommand("warp dungeon_hub");
                 return Constants.SUCCESS;
             }));
+
+            for (int i = 1; i <= 7; i++) {
+                int finalI = i;
+                dispatcher.register(ClientCommandManager.literal("f" + i).executes(context -> {
+                    Misc.executeCommand("joindungeon catacombs " + finalI);
+                    return Constants.SUCCESS;
+                }));
+                dispatcher.register(ClientCommandManager.literal("m" + i).executes(context -> {
+                    Misc.executeCommand("joindungeon master_catacombs " + finalI);
+                    return Constants.SUCCESS;
+                }));
+            }
         }
     }
 }
diff --git a/src/main/java/blade/addon/utils/Location.java b/src/main/java/blade/addon/utils/Location.java
index fa5f98c..72d9da5 100644
--- a/src/main/java/blade/addon/utils/Location.java
+++ b/src/main/java/blade/addon/utils/Location.java
@@ -8,53 +8,51 @@ import net.hypixel.modapi.packet.impl.clientbound.event.ClientboundLocationPacke
 import net.minecraft.text.Text;
 
 public enum Location {
-    NONE,
-    DUNGEON,
-    DUNGEON_HUB,
-    PRIVATE_ISLAND,
-    HUB,
-    GALATEA,
-    THE_PARK,
-    THE_FARMING_ISLANDS,
-    GOLD_MINE,
-    DEEP_CAVERNS,
-    DWARVEN_MINES,
-    CRYSTAL_HOLLOWS,
-    SPIDERS_DEN,
-    THE_END,
-    CRIMSON_ISLE,
-    GARDEN,
-    THE_RIFT,
-    BACKWATER_BAYOU,
-    UNKNOWN,
-    JERRYS_WORKSHOP,
-    MINESHAFT,
-    DARK_AUCTION,
-    KUUDRA;
-
-    private static Location currentLocation = Location.NONE;
+    UNKNOWN("Unknown"),
+    DUNGEON("Dungeon"),
+    DUNGEON_HUB("Dungeon Hub"),
+    PRIVATE_ISLAND("Private Island"),
+    HUB("Hub"),
+    GALATEA("Galatea"),
+    THE_PARK("The Park"),
+    THE_FARMING_ISLANDS("The Farming Islands"),
+    GOLD_MINE("Gold Mine"),
+    DEEP_CAVERNS("Deep Caverns"),
+    DWARVEN_MINES("Dwarven Mines"),
+    CRYSTAL_HOLLOWS("Crystal Hollows"),
+    SPIDERS_DEN("Spider's Den"),
+    THE_END("The End"),
+    CRIMSON_ISLE("Crimson Isle"),
+    GARDEN("Garden"),
+    THE_RIFT("The Rift"),
+    BACKWATER_BAYOU("Backwater Bayou"),
+    JERRYS_WORKSHOP("Jerry's Workshop"),
+    MINESHAFT("Mineshaft"),
+    DARK_AUCTION("Dark Auction"),
+    KUUDRA("Kuudra"),;
+
+    final String name;
+
+    Location(String name) {
+        this.name = name;
+    }
+
+
+    private static Location currentLocation = Location.UNKNOWN;
     private static boolean inSkyblock = false;
     private static boolean detectedNewLocation = false;
+    private static long lastChanged = System.currentTimeMillis();
 
     public static void init() {
 
         HypixelModAPI instance = HypixelModAPI.getInstance();
-        instance.createHandler(ClientboundLocationPacket.class, packet -> packet.getMap().ifPresent(map -> {
-
+        instance.createHandler(ClientboundLocationPacket.class, packet -> packet.getMap().ifPresent(locationName -> {
             if (packet.getServerType().isPresent()) {
                 ServerType serverType = packet.getServerType().get();
                 inSkyblock = serverType.getName().equals("SkyBlock");
             }
 
-            try {
-                currentLocation = Location.valueOf(map.toUpperCase().replace(" ", "_").replaceAll("'", ""));
-            } catch (IllegalArgumentException ignored) {
-                currentLocation = Location.NONE;
-            }
-            detectedNewLocation = true;
-            Debug.sendDebugMessage(Text.literal("Location: " + currentLocation));
-
-            Events.ON_LOCATION_CHANGE.invoke(locationChangeEvent -> locationChangeEvent.onLocationChange(currentLocation));
+            changeLocation(getLocation(locationName));
         }));
 
         instance.subscribeToEventPacket(ClientboundLocationPacket.class);
@@ -65,6 +63,28 @@ public enum Location {
         });
     }
 
+    public static Location getLocation(String locationName) {
+        Location location;
+
+        try {
+            location = Location.valueOf(locationName.toUpperCase().replace(" ", "_").replaceAll("'", ""));
+        } catch (IllegalArgumentException ignored) {
+            location = Location.UNKNOWN;
+        }
+
+        return location;
+    }
+
+    public static void changeLocation(Location location) {
+        currentLocation = location;
+        detectedNewLocation = true;
+        lastChanged = System.currentTimeMillis();
+        Debug.sendDebugMessage(Text.literal("Location: " + currentLocation));
+
+        Events.ON_LOCATION_CHANGE.invoke(locationChangeEvent -> locationChangeEvent.onLocationChange(currentLocation));
+
+    }
+
     public static boolean in(Location location) {
         if (!inSkyblock) return false;
         return currentLocation == location;
@@ -75,11 +95,20 @@ public enum Location {
         return currentLocation == Location.DUNGEON;
     }
 
+    public static Location getCurrentLocation() {
+        return currentLocation;
+    }
+
     public static boolean inSkyblock() {
         return inSkyblock;
     }
 
-    public static boolean hasRecivedLocation() {
+    public static boolean hasReceivedLocation() {
         return detectedNewLocation;
     }
+
+    @Override
+    public String toString() {
+        return "Location: " + name + " Last changed: " +  (System.currentTimeMillis() - lastChanged) / 1000.0 + "s";
+    }
 }
\ No newline at end of file
diff --git a/src/main/java/blade/addon/utils/config/Config.java b/src/main/java/blade/addon/utils/config/Config.java
index c1b1a4d..5aedf40 100644
--- a/src/main/java/blade/addon/utils/config/Config.java
+++ b/src/main/java/blade/addon/utils/config/Config.java
@@ -169,7 +169,8 @@ public class Config {
         ConfigSection goldor = new ConfigSection(Text.literal("Goldor"));
         goldor.add(new ConfigBool(Text.literal("Goldor tick timer"), () -> Floor7.enableGoldorTickTimer, bool -> Floor7.enableGoldorTickTimer = bool));
         goldor.add(new ConfigBool(Text.literal("Term start time"), () -> Floor7.enableTermStartTimer, bool -> Floor7.enableTermStartTimer = bool));
-        goldor.add(new ConfigBool(Text.literal("Make goldor timer tick down"), () -> Floor7.inDeathTicks, bool -> Floor7.inDeathTicks = bool));
+        goldor.add(new ConfigBool(Text.literal("in 3s increments"), () -> Floor7.inDeathTicks, bool -> Floor7.inDeathTicks = bool));
+        goldor.add(new ConfigBool(Text.literal("Tick up (instead of down from 3s)"), () -> Floor7.makeGoldorTickUp, bool -> Floor7.makeGoldorTickUp = bool));
         goldor.add(new ConfigBool(Text.literal("Positional messages"), () -> Floor7.enablePositionalMessages, bool -> Floor7.enablePositionalMessages = bool));
         goldor.add(new ConfigBool(Text.literal("Terminal splits"), () -> Section.enableTerminalSplits, bool -> Section.enableTerminalSplits = bool));
         goldor.add(new ConfigOptions<>(Text.literal("Display when"), Section.DisplayTerminalSplitsWhen.values(), () -> Section.displayTerminalSplitsWhen, when -> Section.displayTerminalSplitsWhen = when));
@@ -202,7 +203,7 @@ public class Config {
         relic.add(new ConfigBool(Text.literal("Use valleys progress bar"), () -> Floor7.useValleyBar, bool -> Floor7.useValleyBar = bool));
         relic.add(new ConfigInt(Text.literal("Relic start timer ticks"), () -> Floor7.relicSpawnTicks, num -> Floor7.relicSpawnTicks = num, 1, 30, 50));
         relic.add(new ConfigBool(Text.literal("Enable relic placed time"), () -> Floor7.enableRelicPlaceTime
…… diff 过长已截断 ……
capitalize formatting of boss bar lettersRyflyy · 2026-02-01 · bca7c96
查看变更
src/main/java/blade/addon/utils/rendering/RenderUtils.java | 6 +++---
 1 file changed, 3 insertions(+), 3 deletions(-)
diff --git a/src/main/java/blade/addon/utils/rendering/RenderUtils.java b/src/main/java/blade/addon/utils/rendering/RenderUtils.java
index 7af9738..f0503cc 100644
--- a/src/main/java/blade/addon/utils/rendering/RenderUtils.java
+++ b/src/main/java/blade/addon/utils/rendering/RenderUtils.java
@@ -119,9 +119,9 @@ public class RenderUtils {
 
 
         public static String formatHealth(float health) {
-            if (health >= 1e9) return String.format("%.1fb", health / 1e9);
-            if (health >= 1e6) return String.format("%.1fm", health / 1e6);
-            if (health >= 1e3) return String.format("%.1fk", health / 1e3);
+            if (health >= 1e9) return String.format("%.1fB", health / 1e9);
+            if (health >= 1e6) return String.format("%.1fM", health / 1e6);
+            if (health >= 1e3) return String.format("%.1fK", health / 1e3);
             return health + "";
         }
 
- added chat filters - added dragon health - added maxor stun duration - added arrow swapper display and title - added f7 boss health numbers in the boss bar, credits to slice for the initial code
- hopefully fixed splits getting sent in between the xp message - fixed term time stamps sending 0:0s for the final completion of each section - hopefully fixed dragon spawn timer - hopefully fixed stuff breaking thanks to hypixels random changes
Gabriel Ellnebrand · 2026-01-30 · ff96863
查看变更
gradle.properties                                                                   |   4 +-
 src/main/java/blade/addon/Bladeaddons.java                                          |  21 ++++++---
 src/main/java/blade/addon/features/dungeon/ChestCounter.java                        |   2 +-
 src/main/java/blade/addon/features/dungeon/ItemHighlight.java                       |   6 +--
 src/main/java/blade/addon/features/dungeon/KeyNotifier.java                         |   7 +--
 src/main/java/blade/addon/features/dungeon/SecretSpawnTimer.java                    |   2 +-
 src/main/java/blade/addon/features/dungeon/f7/DragSpawnTimer.java                   | 157 --------------------------------------------------------------------
 src/main/java/blade/addon/features/dungeon/f7/RelicTimer.java                       |  31 +++++++++-----
 src/main/java/blade/addon/features/dungeon/f7/dragons/DragSpawnTimer.java           | 104 +++++++++++++++++++++++++++++++++++++++++++++
 src/main/java/blade/addon/features/dungeon/f7/dragons/Dragon.java                   |  61 +++++++++++++++++++++++++++
 src/main/java/blade/addon/features/dungeon/f7/dragons/DragonHealth.java             |  87 ++++++++++++++++++++++++++++++++++++++
 src/main/java/blade/addon/features/dungeon/f7/invincibility/InvincibilityTimer.java |   2 +-
 src/main/java/blade/addon/features/dungeon/f7/location/PositionMessage.java         |  16 +++++--
 src/main/java/blade/addon/features/dungeon/f7/location/PositionMessages.java        |  20 ++++-----
 src/main/java/blade/addon/features/dungeon/f7/{ => maxor}/CrystalSpawn.java         |   2 +-
 src/main/java/blade/addon/features/dungeon/f7/maxor/MaxorStun.java                  |  59 ++++++++++++++++++++++++++
 src/main/java/blade/addon/features/dungeon/f7/{ => storm}/DistanceToLedge.java      |   2 +-
 src/main/java/blade/addon/features/dungeon/f7/{ => storm}/PillarExplode.java        |   4 +-
 src/main/java/blade/addon/features/dungeon/f7/{ => storm}/StormTickTimer.java       |   2 +-
 src/main/java/blade/addon/features/dungeon/f7/terms/GoldorTickTimer.java            |   2 +-
 src/main/java/blade/addon/features/dungeon/f7/terms/TermStartTimer.java             |   2 +-
 src/main/java/blade/addon/features/filter/FilterEntry.java                          | 156 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 src/main/java/blade/addon/features/filter/FilterList.java                           |  93 ++++++++++++++++++++++++++++++++++++++++
 src/main/java/blade/addon/features/filter/Filters.java                              |  31 ++++++++++++++
 src/main/java/blade/addon/features/highlight/Highlight.java                         |   4 --
 src/main/java/blade/addon/features/highlight/MimicHighlight.java                    |   4 +-
 src/main/java/blade/addon/features/highlight/TeammateHighlight.java                 |   2 +-
 src/main/java/blade/addon/features/notifications/NotificationEntry.java             |   2 +-
 src/main/java/blade/addon/features/notifications/Notifications.java                 |  14 ++++--
 src/main/java/blade/addon/features/other/ArrowSwapper.java                          |  95 +++++++++++++++++++++++++++++++++++++++++
 src/main/java/blade/addon/features/other/PracticeSS.java                            |  11 ++---
 src/main/java/blade/addon/features/other/RagDisplay.java                            |   2 +-
 src/main/java/blade/addon/mixin/BossBarHudMixin.java                                |  56 ++++++++++++++++++++++++
 src/main/java/blade/addon/mixin/ClientConnectionMixin.java                          |  12 +-----
 src/main/java/blade/addon/mixin/ClientPlayNetworkHandlerMixin.java                  |   9 ++--
 src/main/java/blade/addon/mixin/ItemStackMixin.java                                 |  16 -------
 src/main/java/blade/addon/utils/Constants.java                                      |  14 +++---
 src/main/java/blade/addon/utils/Keybinds.java                                       |   6 +--
 src/main/java/blade/addon/utils/Location.java                                       |  22 +++++++++-
 src/main/java/blade/addon/utils/config/Config.java                                  |  40 +++++++++++-------
 src/main/java/blade/addon/utils/config/FolderUtility.java                           |   1 +
 src/main/java/blade/addon/utils/config/components/CombinedScreenNotifications.java  |  11 +++--
 src/main/java/blade/addon/utils/config/components/CombinedTickTimer.java            |   4 +-
 src/main/java/blade/addon/utils/config/components/Components.java                   |  21 ++++++---
 src/main/java/blade/addon/utils/config/values/Dungeons.java                         |   3 ++
 src/main/java/blade/addon/utils/config/values/ExtraOptions.java                     |   6 +++
 src/main/java/blade/addon/utils/config/values/Floor7.java                           |   8 +++-
 src/main/java/blade/addon/utils/data/EntityUtil.java                                |   6 +++
 src/main/java/blade/addon/utils/data/ItemUtil.java                                  |  13 +++++-
 src/main/java/blade/addon/utils/data/TextUtil.java                                  |   5 +++
 src/main/java/blade/addon/utils/dungeon/DungeonClass.java                           |   2 +-
 src/main/java/blade/addon/utils/dungeon/Phase.java                                  |   4 +-
 src/main/java/blade/addon/utils/dungeon/Section.java                                |  17 ++++----
 src/main/java/blade/addon/utils/events/Events.java                                  |   7 ++-
 src/main/java/blade/addon/utils/events/interfaces/PacketEvent.java                  |   8 ++++
 src/main/java/blade/addon/utils/rendering/RenderUtils.java                          |  25 ++++++++++-
 src/main/resources/blade-addons.mixins.json                                         |   1 +
 57 files changed, 1007 insertions(+), 317 deletions(-)
diff --git a/gradle.properties b/gradle.properties
index 7bb6dfd..7cf85a5 100644
--- a/gradle.properties
+++ b/gradle.properties
@@ -10,10 +10,10 @@ org.gradle.configuration-cache=false
 minecraft_version=1.21.10
 yarn_mappings=1.21.10+build.3
 loader_version=0.18.4
-loom_version=1.14-SNAPSHOT
+loom_version=1.15-SNAPSHOT
 
 # Mod Properties
-mod_version=2.3.2
+mod_version=2.3.3
 maven_group=blade.addon
 archives_base_name=blade-addons
 
diff --git a/src/main/java/blade/addon/Bladeaddons.java b/src/main/java/blade/addon/Bladeaddons.java
index cea7b58..5e9a5bc 100644
--- a/src/main/java/blade/addon/Bladeaddons.java
+++ b/src/main/java/blade/addon/Bladeaddons.java
@@ -12,23 +12,26 @@ import blade.addon.features.dungeon.RunStartValidator;
 import blade.addon.features.dungeon.SecretSpawnTimer;
 import blade.addon.features.dungeon.WarpCooldown;
 import blade.addon.features.dungeon.f7.BossWaypoints;
-import blade.addon.features.dungeon.f7.CrystalSpawn;
-import blade.addon.features.dungeon.f7.DistanceToLedge;
-import blade.addon.features.dungeon.f7.DragSpawnTimer;
-import blade.addon.features.dungeon.f7.PillarExplode;
+import blade.addon.features.dungeon.f7.maxor.CrystalSpawn;
+import blade.addon.features.dungeon.f7.storm.DistanceToLedge;
+import blade.addon.features.dungeon.f7.dragons.DragSpawnTimer;
+import blade.addon.features.dungeon.f7.storm.PillarExplode;
 import blade.addon.features.dungeon.f7.PredevTimer;
 import blade.addon.features.dungeon.f7.RelicTimer;
-import blade.addon.features.dungeon.f7.StormTickTimer;
+import blade.addon.features.dungeon.f7.storm.StormTickTimer;
+import blade.addon.features.dungeon.f7.dragons.DragonHealth;
 import blade.addon.features.dungeon.f7.invincibility.InvincibilityDuration;
 import blade.addon.features.dungeon.f7.invincibility.InvincibilityTimer;
 import blade.addon.features.dungeon.f7.location.LocationNotifier;
 import blade.addon.features.dungeon.f7.location.PositionMessages;
+import blade.addon.features.dungeon.f7.maxor.MaxorStun;
+import blade.addon.features.dungeon.f7.terms.DeviceNotifier;
 import blade.addon.features.dungeon.f7.terms.GoldorTickTimer;
 import blade.addon.features.dungeon.f7.terms.LeapNotification;
 import blade.addon.features.dungeon.f7.terms.MelodyWarning;
-import blade.addon.features.dungeon.f7.terms.DeviceNotifier;
 import blade.addon.features.dungeon.f7.terms.SectionProgress;
 import blade.addon.features.dungeon.f7.terms.TermStartTimer;
+import blade.addon.features.filter.Filters;
 import blade.addon.features.highlight.BatHighlight;
 import blade.addon.features.highlight.MimicHighlight;
 import blade.addon.features.highlight.MobHighlight;
@@ -37,6 +40,7 @@ import blade.addon.features.highlight.TeammateHighlight;
 import blade.addon.features.highlight.WitherHighlight;
 import blade.addon.features.item.DropAnimation;
 import blade.addon.features.notifications.Notifications;
+import blade.addon.features.other.ArrowSwapper;
 import blade.addon.features.other.CompactHoppity;
 import blade.addon.features.other.DianaNotifier;
 import blade.addon.features.other.KickedTimer;
@@ -61,7 +65,6 @@ import blade.addon.utils.dungeon.Section;
 import blade.addon.utils.events.CustomEvents;
 import blade.addon.utils.rendering.RenderingEvents;
 import net.fabricmc.api.ModInitializer;
-import net.fabricmc.fabric.api.message.v1.ServerMessageEvents;
 
 public class Bladeaddons implements ModInitializer {
     @Override
@@ -129,5 +132,9 @@ public class Bladeaddons implements ModInitializer {
         SectionProgress.init();
         BonzoSound.init();
         QuizTimer.init();
+        Filters.init();
+        DragonHealth.init();
+        MaxorStun.init();
+        ArrowSwapper.init();
     }
 }
\ No newline at end of file
diff --git a/src/main/java/blade/addon/features/dungeon/ChestCounter.java b/src/main/java/blade/addon/features/dungeon/ChestCounter.java
index 743a9ee..303c765 100644
--- a/src/main/java/blade/addon/features/dungeon/ChestCounter.java
+++ b/src/main/java/blade/addon/features/dungeon/ChestCounter.java
@@ -74,7 +74,7 @@ public class ChestCounter {
         if (hasUpdatedData) {
             RenderUtils.drawPrefixedText(component, context, "Chests", Math.min(chestDisplayCount + countedChests, 60) + "");
         } else {
-            context.drawText(MinecraftClient.getInstance().textRenderer, Text.literal("go to the dungeon hub"), x, y, Constants.RED_COLOR, true);
+            context.drawText(MinecraftClient.getInstance().textRenderer, Text.literal("go to the dungeon hub"), x, y, Constants.RED, true);
 
         }
     }
diff --git a/src/main/java/blade/addon/features/dungeon/ItemHighlight.java b/src/main/java/blade/addon/features/dungeon/ItemHighlight.java
index 6ceb069..c257f9a 100644
--- a/src/main/java/blade/addon/features/dungeon/ItemHighlight.java
+++ b/src/main/java/blade/addon/features/dungeon/ItemHighlight.java
@@ -92,11 +92,11 @@ public class ItemHighlight {
         if (distance > 20) {
             return 0;
         } else if (distance > 3.5) {
-            return Constants.RED_COLOR;
+            return Constants.RED;
         } else if (item.age > 11){
-            return Constants.GREEN_COLOR;
+            return Constants.GREEN;
         } else {
-            return Constants.ORANGE_COLOR;
+            return Constants.ORANGE;
         }
     }
 
diff --git a/src/main/java/blade/addon/features/dungeon/KeyNotifier.java b/src/main/java/blade/addon/features/dungeon/KeyNotifier.java
index 6990ece..3f09f6d 100644
--- a/src/main/java/blade/addon/features/dungeon/KeyNotifier.java
+++ b/src/main/java/blade/addon/features/dungeon/KeyNotifier.java
@@ -4,6 +4,7 @@ import blade.addon.utils.Location;
 import blade.addon.utils.Scheduler;
 import blade.addon.utils.config.values.Dungeons;
 import blade.addon.utils.dungeon.DungeonClass;
+import blade.addon.utils.dungeon.Phase;
 import blade.addon.utils.events.Events;
 import blade.addon.utils.rendering.RenderUtils;
 import config.practical.hud.HUDComponent;
@@ -42,7 +43,7 @@ public class KeyNotifier {
 
     public static void init() {
         ClientTickEvents.END_WORLD_TICK.register((world) -> {
-            if (!Location.inDungeon() || hasKey || !Dungeons.enableKeyNotifier) return;
+            if (!Location.inDungeon() || hasKey || !Dungeons.enableKeyNotifier || Phase.inBoss()) return;
 
             for (Entity entity : world.getEntities()) {
                 if (entity instanceof ArmorStandEntity armorStand && !foundKeys.contains(entity)) {
@@ -106,11 +107,11 @@ public class KeyNotifier {
     }
 
     private static boolean isValidClass() {
-       return Dungeons.displayKeyForAllClasses || DungeonClass.isClass(DungeonClass.ARCHER) || DungeonClass.isClass(DungeonClass.MAGE);
+       return (Dungeons.displayKeyForAllClasses || DungeonClass.isClass(DungeonClass.ARCHER) || DungeonClass.isClass(DungeonClass.MAGE) && Dungeons.enableKeyNotifier);
     }
 
     public static boolean display() {
-        return Location.inDungeon() && hasKey && isValidClass();
+        return Location.inDungeon() && hasKey && isValidClass() && !Phase.inBoss();
     }
 
     public static void render(HUDComponent component, DrawContext context) {
diff --git a/src/main/java/blade/addon/features/dungeon/SecretSpawnTimer.java b/src/main/java/blade/addon/features/dungeon/SecretSpawnTimer.java
index fb5e55b..6032314 100644
--- a/src/main/java/blade/addon/features/dungeon/SecretSpawnTimer.java
+++ b/src/main/java/blade/addon/features/dungeon/SecretSpawnTimer.java
@@ -46,7 +46,7 @@ public class SecretSpawnTimer {
         int x = component.getScaledX();
         int y = component.getScaledY();
 
-        int color = tick > 10 ? Constants.GREEN_COLOR : tick > 5 ? Constants.ORANGE_COLOR : Constants.RED_COLOR;
+        int color = tick > 10 ? Constants.GREEN : tick > 5 ? Constants.ORANGE : Constants.RED;
 
         RenderUtils.drawCenteredText(context, MinecraftClient.getInstance().textRenderer, Text.literal(tick + ""), x, y, component.getWidth(), color);
 
diff --git a/src/main/java/blade/addon/features/dungeon/f7/DragSpawnTimer.java b/src/main/java/blade/addon/features/dungeon/f7/DragSpawnTimer.java
deleted file mode 100644
index f065e03..0000000
--- a/src/main/java/blade/addon/features/dungeon/f7/DragSpawnTimer.java
+++ /dev/null
@@ -1,157 +0,0 @@
-package blade.addon.features.dungeon.f7;
-
-import blade.addon.utils.Scheduler;
-import blade.addon.utils.config.values.Floor7;
-import blade.addon.utils.dungeon.DungeonClass;
-import blade.addon.utils.events.Events;
-import blade.addon.utils.rendering.RenderUtils;
-import config.practical.hud.HUDComponent;
-import net.minecraft.client.gui.DrawContext;
-import net.minecraft.particle.ParticleTypes;
-import net.minecraft.sound.SoundEvents;
-
-public class DragSpawnTimer {
-
-    enum Dragon {
-        PURPLE(0xffff55ff, 0, 4),
-        BLUE(0xff55ffff, 1, 3),
-        RED(0xffff5555, 2, 2),
-        GREEN(0xff55ff55, 3, 1),
-        ORANGE(0xffffaa00, 4, 0),
-        NONE(0xffffffff, 99, 99);
-
-        final int color;
-        final int archPrio;
-        final int bersPrio;
-
-        Dragon(int color, int archPrio, int bersPrio) {
-            this.color = color;
-            this.archPrio = archPrio;
-            this.bersPrio = bersPrio;
-        }
-    }
-
-    public enum Team {
-        ARCHER_TEAM("Archer team"), BERS_TEAM("Bers team");
-
-        private final String label;
-
-        Team(String label) {
-            this.label = label;
-        }
-
-        @Override
-        public String toString() {
-            return label;
-        }
-    }
-
-    private static final int SPAWN_DURATION = 100;
-    private static final int DUPE_DELAY = 130;
-
-    private static Dragon currentDragon = Dragon.NONE;
-    private static Dragon prevDragon = Dragon.NONE;
-    private static boolean hasDoneSplit = false;
-    private static int tick = 0;
-    private static int dupeTick = 0;
-
-    public static void init() {
-        Events.ON_PARTICLE.register((x, y, z, effect) -> {
-            if (effect.getType() != ParticleTypes.ENCHANT) return false;
-            Dragon detectedDragon = getDragon(x, y, z);
-
-            if (detectedDragon == Dragon.NONE) return false;
-
-            //the dupe check is to stop purple from just breaking it when it spawns
-            if (currentDragon == Dragon.NONE) {
-                if ((prevDragon == detectedDragon && dupeTick > 0)) return false;
-                currentDragon = detectedDragon;
-                prevDragon = currentDragon;
-                dupeTick = DUPE_DELAY;
-                tick = SPAWN_DURATION;
-                if (Floor7.sendSoundOnDragSpawn) {
-                    Scheduler.scheduleSound(SoundEvents.BLOCK_NOTE_BLOCK_PLING.value(), 0.75f, 1);
-                }
-            } else if (detectedDragon != currentDragon && !hasDoneSplit) {
-                currentDragon = getPrio(detectedDragon, currentDragon);
-                prevDragon = currentDragon;
-                dupeTick = DUPE_DELAY * 2;
-                hasDoneSplit = true;
-            }
-
-            return false;
-        });
-
-        Events.ON_SERVER_TICK.register(() -> {
-            tick = Math.max(tick - 1, 0);
-            dupeTick = Math.max(dupeTick - 1, 0);
-            if (dupeTick == 0 && prevDragon != Dragon.NONE) {
-                prevDragon = Dragon.NONE;
-            }
-
-            if (tick == 0 && currentDragon != Dragon.NONE) {
-                currentDragon = Dragon.NONE;
-            }
-            return false;
-        });
-
-        Events.ON_LOCATION_CHANGE.register(newLocation -> {
-            reset();
-            return false;
-        });
-    }
-
-    private static void reset() {
-        hasDoneSplit = false;
-        currentDragon = Dragon.NONE;
-        prevDragon = Dragon.NONE;
-        tick = 0;
-    }
-
-    //checks are from valley addons
-    private static Dragon getDragon(double x, double y, double z) {
-        if (y >= 14 && y <= 19) {
-            if (x >= 27 && x <= 32) {
-                if (z == 59) {
-                    return Dragon.RED;
-                } else if (z == 94) {
-                    return Dragon.GREEN;
-                }
-            } else if (x >= 79 && x <= 85) {
-                if (z == 94) {
-                    return Dragon.BLUE;
-                } else if (z == 56) {
-                    return Dragon.ORANGE;
-                }
-            } else if (x == 56) {
-                return Dragon.PURPLE;
-            }
-        }
-        return Dragon.NONE;
-    }
-
-    private static Dragon getPrio(Dragon dragon1, Dragon dragon2) {
-        if (DungeonClass.isArchTeam()) {
-            if (dragon1.archPrio < dragon2.archPrio) {
-                return dragon1;
-            } else {
-                return dragon2;
-            }
-        } else if (DungeonClass.isBersTeam()) {
-            if (dragon1.bersPrio < dragon2.bersPrio) {
-                return dragon1;
-            } else {
-                return dragon2;
-            }
-        }
-        return Dragon.NONE;
-    }
-
-    public static boolean display() {
-        return Floor7.dragSpawnTimers && currentDragon != Dragon.NONE;
-    }
-
-    public static void render(HUDComponent component, DrawContext context) {
-        RenderUtils.drawTimer(component, context, tick, currentDragon.color);
-    }
-}
diff --git a/src/main/java/blade/addon/features/dungeon/f7/RelicTimer.java b/src/main/java/blade/addon/features/dungeon/f7/RelicTimer.java
index 8fbf1fc..db76f81 100644
--- a/src/main/java/blade/addon/features/dungeon/f7/RelicTimer.java
+++ b/src/main/java/blade/addon/features/dungeon/f7/RelicTimer.java
@@ -1,11 +1,11 @@
 package blade.addon.features.dungeon.f7;
 
-import blade.addon.utils.Constants;
 import blade.addon.utils.Location;
 import blade.addon.utils.Misc;
 import blade.addon.utils.config.values.Floor7;
 import blade.addon.utils.data.EntityUtil;
 import blade.addon.utils.data.ItemUtil;
+import blade.addon.utils.data.TextUtil;
 import blade.addon.utils.debug.Debug;
 import blade.addon.utils.dungeon.Phase;
 import blade.addon.utils.events.Events;
@@ -21,8 +21,8 @@ import net.minecraft.client.render.VertexConsumer;
 import net.minecraft.client.render.VertexRendering;
 import net.minecraft.client.util.math.MatrixStack;
 import net.minecraft.entity.Entity;
-import net.minecraft.entity.EquipmentSlot;
 import net.minecraft.entity.decoration.ArmorStandEntity;
+import net.minecraft.item.ItemStack;
 import net.minecraft.text.MutableText;
 import net.minecraft.text.Text;
 import net.minecraft.util.Formatting;
@@ -44,7 +44,7 @@ public class RelicTimer {
         final Box box;
         final int color;
         final String name;
-        long placedTime = 0;
+        int placedTick = 0;
 
         Relic(Box box, int color, String name) {
             this.box = box;
@@ -63,6 +63,7 @@ public class RelicTimer {
     private static Relic pickedupRelic = null;
 
     private static int tick = Floor7.relicSpawnTicks;
+    private static int placeTick = 0;
     private static boolean sentRelicTimes = false;
 
     //only here to test the relic progress bar
@@ -79,6 +80,10 @@ public class RelicTimer {
                 forceGUI = false;
             }
 
+            if (Phase.inP5()) {
+                placeTick++;
+            }
+
             return false;
         });
 
@@ -93,6 +98,7 @@ public class RelicTimer {
             if (Phase.inP5()) {
                 phaseStartTime = System.currentTimeMillis();
                 tick = Floor7.relicSpawnTicks;
+                placeTick = 0;
             }
             return false;
         });
@@ -121,14 +127,19 @@ public class RelicTimer {
 
             for (Entity entity : entities) {
                 if (entity instanceof ArmorStandEntity armorStand) {
-                    if (!EntityUtil.isWearing(armorStand, EquipmentSlot.HEAD, "Relic")) continue;
+                    ItemStack item = armorStand.getWeaponStack();
+                    String id = ItemUtil.getId(item);
+
+                    if (id == null || !id.contains("RELIC")) continue;
+                    Debug.sendDebugMessage(Text.literal("item id is " + id));
 
                     double ax = armorStand.getX();
                     double az = armorStand.getZ();
 
                     for (Relic relic : Relic.values()) {
-                        if (relic.placedTime != 0 && Misc.getDistance(relic.box.maxX, relic.box.maxZ, ax, az) < 1) {
-                            relic.placedTime = System.currentTimeMillis();
+                        if (relic.placedTick != 0 && Misc.getDistance(relic.box.maxX, relic.box.maxZ, ax, az) < 1.5) {
+                            relic.placedTick = placeTick;
+                            Debug.sendDebugMessage(Text.literal("Set" + relic.name + " time to " + relic.placedTick + ", item id is " + id));
                         }
                     }
                 }
@@ -136,8 +147,7 @@ public class RelicTimer {
 
             if (allRelicsPlaced()) {
                 for (Relic relic : Relic.values()) {
-                    long time = relic.placedTime - phaseStartTime;
-                    Misc.addChatMessage(Text.literal(relic.name + " &aRelic placed in &e" + Constants.DECIMAL_FORMAT.format(time) + "s&a."));
+                    Misc.addChatMessage(Text.literal(relic.name + " &aRelic placed in &e" + TextUtil.formatTicks(relic.placedTick) + "s&a."));
                     sentRelicTimes = true;
                 }
             }
@@ -214,14 +224,15 @@ public class RelicTimer {
         tick = Floor7.relicSpawnTicks;
         pickedupRelic = null;
         for (Relic relic : Relic.values()) {
-            relic.placedTime = 0;
+            relic.placedTick = 0;
         }
         sentRelicTimes = false;
+        placeTick = 0;
     }
 
     private static boolean allRelicsPlaced() {
         for (Relic relic : Relic.values()) {
-            if (relic.placedTime == 0) return false;
+            if (relic.placedTick == 0) return false;
         }
         return true;
     }
diff --git a/src/main/java/blade/addon/features/dungeon/f7/dragons/DragSpawnTimer.java b/src/main/java/blade/addon/features/dungeon/f7/dragons/DragSpawnTimer.java
new file mode 100644
index 0000000..52b5086
--- /dev/null
+++ b/src/main/java/blade/addon/features/dungeon/f7/dragons/DragSpawnTimer.java
@@ -0,0 +1,104 @@
+package blade.addon.features.dungeon.f7.dragons;
+
+import blade.addon.utils.Misc;
+import blade.addon.utils.config.values.Floor7;
+import blade.addon.utils.events.Events;
+import blade.addon.utils.rendering.RenderUtils;
+import config.practical.hud.HUDComponent;
+import net.minecraft.client.gui.DrawContext;
+import net.minecraft.network.packet.s2c.play.ParticleS2CPacket;
+import net.minecraft.particle.ParticleTypes;
+import net.minecraft.sound.SoundEvents;
+
+public class DragSpawnTimer {
+
+    public enum Team {
+        ARCHER_TEAM("Archer team"), BERS_TEAM("Bers team");
+
+        private final String label;
+
+        Team(String label) {
+            this.label = label;
+        }
+
+        @Override
+        public String toString() {
+            return label;
+        }
+    }
+
+    private static final int SPAWN_DURATION = 100;
+
+    private static Dragon currentDragon = Dragon.NONE;
+    private static boolean hasDoneSplit = false;
+    private static int tick = 0;
+
+    public static void init() {
+        Events.ON_PACKET.register(packet -> {
+            if (packet instanceof ParticleS2CPacket particle) {
+                if (!validParticle(particle)) return false;
+                Dragon dragon = Dragon.getDragon(particle.getX(), particle.getY(), particle.getZ());
+                testDragon(dragon);
+            }
+
+            return false;
+        });
+
+        Events.ON_SERVER_TICK.register(() -> {
+            tick = Math.max(tick - 1, 0);
+
+            if (tick == 0 && currentDragon != Dragon.NONE) {
+                currentDragon = Dragon.NONE;
+            }
+            return false;
+        });
+
+        Events.ON_LOCATION_CHANGE.register(newLocation -> {
+            reset();
+            return false;
+        });
+    }
+
+    private static void testDragon(Dragon dragon) {
+        if (dragon == Dragon.NONE) return;
+
+        if (currentDragon == Dragon.NONE) {
+            currentDragon = dragon;
+            tick = SPAWN_DURATION;
+
+            if (Floor7.sendSoundOnDragSpawn) {
+                Misc.sendSound(SoundEvents.BLOCK_NOTE_BLOCK_PLING.value(), 0.75f, 1);
+            }
+
+        } else if (currentDragon != dragon &&  !hasDoneSplit) {
+            currentDragon = Dragon.getPrio(dragon, currentDragon);
+            hasDoneSplit = true;
+        }
+    }
+
+    private static boolean validParticle(ParticleS2CPacket packet) {
+        if (packet.getCount() != 20) return false;
+        if (packet.getY() != 19) return false;
+        if (packet.getParameters().getType() != ParticleTypes.FLAME) return false;
+        if (packet.getOffsetX() != 2) return false;
+        if (packet.getOffsetY() != 3) return false;
+        if (packet.getOffsetZ() != 2) return false;
+        if (packet.getX() % 1 != 0) return false;
+        if (packet.getZ() % 1 != 0) return false;
+        return true;
+    }
+
+    private static void reset() {
+        hasDoneSplit = false;
+        currentDragon = Dragon.NONE;
+        tick = 0;
+    }
+
+    public static boolean display() {
+        return Floor7.dragSpawnTimers && currentDragon != Dragon.NONE;
+    }
+
+    public static void render(HUDComponent component, DrawContext context) {
+        RenderUtils.drawTimer(component, context, tick, currentDragon.color);
+    }
+}
diff --git a/src/main/java/blade/addon/features/dungeon/f7/dragons/Dragon.java b/src/main/java/blade/addon/features/dungeon/f7/dragons/Dragon.java
new file mode 100644
index 0000000..b2803a9
--- /dev/null
+++ b/src/main/java/blade/addon/features/dungeon/f7/dragons/Dragon.java
@@ -0,0 +1,61 @@
+package blade.addon.features.dungeon.f7.dragons;
+
+import blade.addon.utils.dungeon.DungeonClass;
+
+public     enum Dragon {
+    PURPLE(0xffff55ff, 0, 4),
+    BLUE(0xff55ffff, 1, 3),
+    RED(0xffff5555, 2, 2),
+    GREEN(0xff55ff55, 3, 1),
+    ORANGE(0xffffaa00, 4, 0),
+    NONE(0xffffffff, 99, 99);
+
+    final int color;
+    final int archPrio;
+    final int bersPrio;
+
+    Dragon(int color, int archPrio, int bersPrio) {
+        this.color = color;
+        this.archPrio = archPrio;
+        this.bersPrio = bersPrio;
+    }
+
+    //checks are from valley addons
+    public static Dragon getDragon(double x, double y, double z) {
+        if (y >= 14 && y <= 19) {
+            if (x >= 27 && x <= 32) {
+                if (z == 59) {
+                    return Dragon.RED;
+                } else if (z == 94) {
+                    return Dragon.GREEN;
+                }
+            } else if (x >= 79 && x <= 85) {
+                if (z == 94) {
+                    return Dragon.BLUE;
+                } else if (z == 56) {
+                    return Dragon.ORANGE;
+                }
+            } else if (x == 56) {
+                return Dragon.PURPLE;
+            }
+        }
+        return Dragon.NONE;
+    }
+
+    public static Dragon getPrio(Dragon dragon1, Dragon dragon2) {
+        if (DungeonClass.isArchTeam()) {
+            if (dragon1.archPrio < dragon2.archPrio) {
+                return dragon1;
+            } else {
+                return dragon2;
+            }
+        } else if (DungeonClass.isBersTeam()) {
+            if (dragon1.bersPrio < dragon2.bersPrio) {
+                return dragon1;
+            } else {
+                return dragon2;
+            }
+        }
+        return Dragon.NONE;
+    }
+}
diff --git a/src/main/java/blade/addon/features/dungeon/f7/dragons/DragonHealth.java b/src/main/java/blade/addon/features/dungeon/f7/dragons/DragonHealth.java
new file mode 100644
index 0000000..fbfd28a
--- /dev/null
+++ b/src/main/java/blade/addon/features/dungeon/f7/dragons/DragonHealth.java
@@ -0,0 +1,87 @@
+package blade.addon.features.dungeon.f7.dragons;
+
+import blade.addon.utils.Constants;
+import blade.addon.utils.Location;
+import blade.addon.utils.config.values.Floor7;
+import blade.addon.utils.data.EntityUtil;
+import blade.addon.utils.events.Events;
+import blade.addon.utils.rendering.RenderUtils;
+import blade.addon.utils.rendering.RenderingEvents;
+import net.fabricmc.fabric.api.client.event.lifecycle.v1.ClientTickEvents;
+import net.fabricmc.fabric.api.client.rendering.v1.world.WorldRenderContext;
+import net.minecraft.client.render.VertexConsumer;
+import net.minecraft.client.util.math.MatrixStack;
+import net.minecraft.entity.boss.dragon.EnderDragonEntity;
+import net.minecraft.text.Text;
+import net.minecraft.util.math.Vec3d;
+
+import java.util.concurrent.ConcurrentLinkedQueue;
+
+public class DragonHealth {
+
+    static class DataHolder {
+        EnderDragonEntity dragon;
+        float health;
+
+        public DataHolder(EnderDragonEntity dragon, float health) {
+            this.dragon = dragon;
+            this.health = health;
+        }
+    }
+
+    static final ConcurrentLinkedQueue<DataHolder> dragons = new ConcurrentLinkedQueue<>();
+
+    public static void init() {
+
+        Events.ON_ENTITY_SPAWNED.register((entity, world) -> {
+            if (!Location.inDungeon()) return false;
+
+            if (entity instanceof EnderDragonEntity dragon) {
+                dragons.add(new DataHolder(dragon, dragon.getHealth()));
+            }
+
+            return false;
+        });
+
+        ClientTickEvents.END_WORLD_TICK.register(world -> dragons.removeIf(dataHolder -> dataHolder.dragon.isRemoved()));
+        RenderingEvents.FILLED_ENTITY.register(DragonHealth::render);
+        Events.ON_LOCATION_CHANGE.register(newLocation -> {
+            reset();
+            return false;
+        });
+    }
+
+    private static void reset() {
+        dragons.clear();
+    }
+
+    private static int getColor(float health) {
+        if (health >= 7.5e8) return Constants.GREEN;
+        if (health >= 5e8) return Constants.YELLOW;
+        if (health >= 2.5e8) return Constants.GOLD;
+        return Constants.RED;
+    }
+
+    private static void render(WorldRenderContext context, MatrixStack matrixStack, VertexConsumer consumer) {
+        if (!Floor7.dragonHealth) return;
+
+        dragons.forEach(dataHolder -> {
+
+            EnderDragonEntity dragon = dataHolder.dragon;
+            float currHealth =  dragon.getHealth();
+
+            if (currHealth != 1024.0f) {
+                dataHolder.health = currHealth;
+            }
+
+            float health = dataHolder.health;
+
+            if (health == 0) return;
+            Vec3d pos = EntityUtil.getLerpedPos(dragon);
+            RenderUtils.renderText(context, matrixStack, Text.literal(RenderUtils.formatHealth(health)).withColor(getColor(health)), pos, 5);
+
+        });
+
+    }
+
+}
diff --git a/src/main/java/blade/addon/features/dungeon/f7/invincibility/InvincibilityTimer.java b/src/main/java/blade/addon/features/dungeon/f7/invincibility/InvincibilityTimer.java
index e9e9de7..af4c3f0 100644
--- a/src/main/java/blade/addon/features/dungeon/f7/invincibility/InvincibilityTimer.java
+++ b/src/main/java/blade/addon/features/dungeon/f7/invincibility/InvincibilityTimer.java
@@ -154,7 +154,7 @@ public class InvincibilityTimer {
     }
 
     private static void drawSprite(DrawContext context, Identifier identifier, int x, int y, boolean isOn, int ticks, Text timerText) {
-        int color = (ticks > 0? Constants.RED_COLOR: isOn? Constants.YELLOW_COLOR: Constants.GREEN_COLOR);
+        int color = (ticks > 0? Constants.RED: isOn? Constants.YELLOW: Constants.GREEN);
         context.fill(x, y, x + SPRITE_SIZE, y + SPRITE_SIZE, color);
         context.drawGuiTexture(RenderPipelines.GUI_TEXTURED, identifier, x, y, SPRITE_SIZE, SPRITE_SIZE, 0xffffffff);
         if (ticks > 0) {
diff --git a/src/main/java/blade/addon/features/dungeon/f7/location/PositionMessage.java b/src/main/java/blade/addon/features/dungeon/f7/location/PositionMessage.java
index 3c05ebe..0292df8 100644
--- a/src/main/java/blade/addon/features/dungeon/f7/location/PositionMessage.java
+++ b/src/main/java/blade/addon/features/dungeon/f7/location/PositionMessage.java
@@ -9,14 +9,15 @@ import net.minecraft.util.math.Vec3d;
 
 public class PositionMessage {
 
-    private final String message, check;
+    private final String message;
+    private final String[] checks;
     private final Box box;
     private boolean sent;
     private final int[] sections;
 
-    public PositionMessage(String message, String check, Box box, int[] sections) {
+    public PositionMessage(String message, String[] checks, Box box, int[] sections) {
         this.message = message;
-        this.check = check;
+        this.checks = checks;
         this.box = box;
         this.sent = true;
         this.sections = sections;
@@ -42,8 +43,15 @@ public class PositionMessage {
         }
     }
 
+    private boolean matchesACheck(String string) {
+        for (String check : checks) {
+            if (string.contains(check)) return true;
+        }
+        return false;
+    }
+
     public boolean hasBeenSent(String string) {
-        if (!string.toLowerCase().contains(check)) return false;
+        if (!matchesACheck(string.toLowerCase())) return false;
 
         if (sent) {
             return true;
diff --git a/src/main/java/blade/addon/features/dungeon/f7/location/PositionMessages.java b/src/main/java/blade/addon/features/dungeon/f7/location/PositionMessages.java
index 789c449..9ce9033 100644
--- a/src/main/java/blade/addon/features/dungeon/f7/location/PositionMessages.java
+++ b/src/main/java/blade/addon/features/dungeon/f7/location/PositionMessages.java
@@ -21,16 +21,16 @@ public class PositionMessages {
 
     static final ArrayList<PositionMessage> positionMessages = new ArrayList<>();
 
-    static PositionMessage SS = new PositionMessage("At SS!", "ss", new Box(107, 120, 93, 110, 121, 95), new int[]{0, 1});
-    static PositionMessage EE2 = new PositionMessage("At EE2!", "ee2", new Box(57, 109, 130, 59, 110, 132), new int[]{1, 2});
-    static PositionMessage HEE2 = new PositionMessage("At High EE2!", "ee2", new Box(59, 132, 138, 62, 133, 140), new int[]{1, 2});
-    static PositionMessage EE3 = new PositionMessage("At EE3!", "ee3", new Box(1, 109, 103, 3, 110, 106), new int[]{2, 3});
-    static PositionMessage CORE = new PositionMessage("At Core!", "core", new Box(53, 115, 51, 56, 116, 54), new int[]{2, 3, 4});
-    static PositionMessage TUNNEL = new PositionMessage("Inside Goldor Tunnel!", "tunnel", new Box(52, 114, 55, 57, 116, 58), new int[]{4, 5});
-    static PositionMessage SAFE_2 = new PositionMessage("At 2 Safespot!", "2 safespot", new Box(46, 109, 121.987, 49, 110, 121.988), new int[]{1, 2});
-    static PositionMessage SAFE_3 = new PositionMessage("At 3 Safespot!", "3 safespot", new Box(18, 121, 91, 19, 126, 99), new int[]{2, 3});
-    static PositionMessage SPLIT_2 = new PositionMessage("At Split ee2!", "split ee2", new Box(58, 119, 124, 60, 123, 126), new int[]{1, 2});
-    static PositionMessage SAFE_2_HIGH = new PositionMessage("At High 2 Safespot!", "2 safespot", new Box(70, 127, 143.7, 57, 133, 146.7), new int[]{1, 2});
+    static PositionMessage SS = new PositionMessage("At SS!", new String[]{"ss"}, new Box(107, 120, 93, 110, 121, 95), new int[]{0, 1});
+    static PositionMessage EE2 = new PositionMessage("At EE2!", new String[]{"ee2", "early enter 2"}, new Box(57, 109, 130, 59, 110, 132), new int[]{1, 2});
+    static PositionMessage HEE2 = new PositionMessage("At High EE2!", new String[]{"high ee2", "highee2", "hee2"}, new Box(59, 132, 138, 62, 133, 140), new int[]{1, 2});
+    static PositionMessage EE3 = new PositionMessage("At EE3!", new String[]{"ee3", "early enter 3", "early entry 3"}, new Box(1, 109, 103, 3, 110, 106), new int[]{2, 3});
+    static PositionMessage CORE = new PositionMessage("At Core!", new String[]{"core"}, new Box(53, 115, 51, 56, 116, 54), new int[]{2, 3, 4});
+    static PositionMessage TUNNEL = new PositionMessage("Inside Goldor Tunnel!", new String[]{"tunnel"}, new Box(52, 114, 55, 57, 116, 58), new int[]{4, 5});
+    static PositionMessage SAFE_2 = new PositionMessage("At 2 Safespot!", new String[]{"2 safespot", "ee2 safespot", "safespot ee2", "s2 safespot", "safespot s2"}, new Box(46, 109, 121.987, 49, 110, 121.988), new int[]{1, 2});
+    static PositionMessage SAFE_3 = new PositionMessage("At 3 Safespot!", new String[]{"3 safespot", "ee3 safespot", "freaky ee3", "freak ee", "safespot s3", "safespot 3"}, new Box(18, 121, 91, 19, 126, 99), new int[]{2, 3});
+    static PositionMessage SPLIT_2 = new PositionMessage("At Split ee2!", new String[]{"split ee2", "splitee2", "mage term"}, new Box(58, 119, 124, 60, 123, 126), new int[]{1, 2});
+    static PositionMessage SAFE_2_HIGH = new PositionMessage("At High 2 Safespot!", new String[]{"2 safespot"}, new Box(70, 127, 143.7, 57, 133, 146.7), new int[]{1, 2});
 
     public static void init() {
         ClientTickEvents.END_CLIENT_TICK.register(PositionMessages::tick);
diff --git a/src/main/java/blade/addon/features/dungeon/f7/CrystalSpawn.java b/src/main/java/blade/addon/features/dungeon/f7/maxor/CrystalSpawn.java
similarity index 98%
rename from src/main/java/blade/addon/features/dungeon/f7/CrystalSpawn.java
rename to src/main/java/blade/addon/features/dungeon/f7/maxor/CrystalSpawn.java
index 3c36a48..963595c 100644
--- a/src/main/java/blade/addon/features/dungeon/f7/CrystalSpawn.java
+++ b/src/main/java/blade/addon/features/dungeon/f7/maxor/CrystalSpawn.java
@@ -1,4 +1,4 @@
-package blade.addon.features.dungeon.f7;
+package blade.addon.features.dungeon.f7.maxor;
 
 import blade.addon.utils.Constants;
 import blade.addon.utils.Location;
diff --git a/src/main/java/blade/addon/features/dungeon/f7/maxor/MaxorStun.java b/src/main/java/blade/addon/features/dungeon/f7/maxor/MaxorStun.java
new file mode 100644
index 0000000..592fc60
--- /dev/null
+++ b/src/main/java/blade/addon/features/dungeon/f7/maxor/MaxorStun.java
@@ -0,0 +1,59 @@
+package blade.addon.features.dungeon.f7.maxor;
+
+import blade.addon.utils.Location;
+import blade.addon.utils.config.values.Floor7;
+import blade.addon.utils.data.TextUtil;
+import blade.addon.utils.dungeon.Phase;
+import blade.addon.utils.events.Events;
+import blade.addon.utils.rendering.RenderUtils;
+import config.practical.hud.HUDComponent;
+import net.minecraft.client.gui.DrawContext;
+
+public class MaxorStun {
+
+    private static final int TOTAL_TICKS = 240;
+
+    private static int tick = 0;
+    private static boolean stunned = false;
+
+    public static void init() {
+
+        Events.ON_GAME_MESSAGE.register(text -> {
+            if (!Location.inDungeon() || !Phase.inP1())
+                return false;
+            String string = text.getString();
+
+            if (string.equals("[BOSS] Maxor: THAT BEAM! IT HURTS! IT HURTS!!") || string.equals("[BOSS] Maxor: YOU TRICKED ME!")) {
+                stunned = true;
+            } else if (string.equals("⚠ Maxor is enraged! ⚠")) {
+                tick = TOTAL_TICKS;
+                stunned = false;
+            }
+
+            return false;
+        });
+
+        Events.ON_SERVER_TICK.register(() -> {
+            if (stunned) {
+                tick = Math.max(0, tick - 1);
+            }
+            return false;
+        });
+
+        Events.ON_LOCATION_CHANGE.register(newLocation -> {
+            stunned = false;
+            tick = TOTAL_TICKS;
+            return false;
+        });
+    }
+
+    public static boolean display() {
+        return tick > 0 && Location.inDungeon() && Phase.inP1() && Floor7.maxorStunDuration && stunned;
+    }
+
+    public static void render(HUDComponent component, DrawContext context) {
+        RenderUtils.drawPrefixedText(component, context, "Stunned", TextUtil.formatTicks(tick));
+    }
+
+
+}
diff --git a/src/main/java/blade/addon/features/dungeon/f7/DistanceToLedge.java b/src/main/java/blade/addon/features/dungeon/f7/storm/DistanceToLedge.java
similarity index 97%
rename from src/main/java/blade/addon/features/dungeon/f7/DistanceToLedge.java
rename to src/main/java/blade/addon/features/dungeon/f7/storm/DistanceToLedge.java
index 5ee1a17..6af556a 100644
--- a/src/main/java/blade/addon/features/dungeon/f7/DistanceToLedge.java
+++ b/src/main/java/blade/addon/features/dungeon/f7/storm/DistanceToLedge.java
@@ -1,4 +1,4 @@
-package blade.addon.features.dungeon.f7;
+package blade.addon.features.dungeon.f7.storm;
 
 import blade.addon.utils.Location;
 import blade.addon.utils.config.values.Floor7;
diff --git a/src/main/java/blade/addon/features/dungeon/f7/PillarExplode.java b/src/main/java/blade/addon/features/dungeon/f7/storm/PillarExplode.java
similarity index 94%
rename from src/main/java/blade/addon/features/dungeon/f7/PillarExplode.java
rename to src/main/java/blade/addon/features/dungeon/f7/storm/PillarExplode.java
index 92e30c9..fafa957 100644
--- a/src/main/java/blade/addon/features/dungeon/f7/PillarExplode.java
+++ b/src/main/java/blade/addon/features/dungeon/f7/storm/PillarExplode.java
@@ -1,4 +1,4 @@
-package blade.addon.features.dungeon.f7;
+package blade.addon.features.dungeon.f7.storm;
 
 import blade.addon.utils.Constants;
 import blade.addon.utils.Location;
@@ -44,7 +44,7 @@ public class PillarExplode {
     }
 
     public static void renderTimer(HUDComponent component, DrawContext context) {
-        int color = tick < 6 ? Constants.GREEN_COLOR : Constants.RED_COLOR;
+        int color = tick < 6 ? Constants.GREEN : Constants.RED;
         RenderUtils.drawTimer(component, context, tick, color);
     }
 
diff --git a/src/main/java/blade/addon/features/dungeon/f7/StormTickTimer.java b/src/main/java/blade/addon/features/dungeon/f7/storm/StormTickTimer.java
similarity index 98%
rename from src/main/java/blade/addon/features/dungeon/f7/StormTickTimer.java
rename to src/main/java/blade/addon/features/dungeon/f7/storm/StormTickTimer.java
index ca7b86b..7af2630 100644
--- a/src/main/java/blade/addon/features/dungeon/f7/StormTickTimer.java
+++ b/src/main/java/blade/addon/features/dungeon/f7/storm/StormTickTimer.java
@@ -1,4 +1,4 @@
-package blade.addon.features.dungeon.f7;
+package blade.addon.features.dungeon.f7.storm;
 
 import blade.addon.features.dungeon.f7.invincibility.InvincibilityTimer;
 import blade.addon.utils.Constants;
diff --git a/src/main/java/blade/addon/features/dungeon/f7/terms/GoldorTickTimer.java b/src/main/java/blade/addon/features/dungeon/f7/terms/GoldorTickTimer.java
index 4d9242f..cd71430 100644
--- a/src/main/java/blade/addon/features/dungeon/f7/terms/GoldorTickTimer.java
+++ b/src/main/java/blade/addon/features/dungeon/f7/terms/GoldorTickTimer.java
@@ -34,7 +34,7 @@ public class GoldorTickTimer {
     public static void render(HUDComponent component, DrawContext context) {
         double num = tick * Constants.TICK_DURATION;
         double mod = num % 3;
-        int color = (mod < 1? Constants.GREEN_COLOR: mod < 2? Constants.ORANGE_COLOR: Constants.RED_COLOR);
+        int color = (mod < 1? Constants.GREEN: mod < 2? Constants.ORANGE: Constants.RED);
         if (Floor7.inDeathTicks) num = mod;
 
         RenderUtils.drawTimer(component, context, num, color);
diff --git a/src/main/java/blade/addon/features/dungeon/f7/terms/TermStartTimer.java b/src/main/java/blade/addon/features/dungeon/f7/terms/TermStartTimer.java
index 4789a24..fe76060 100644
--- a/src/main/java/blade/addon/features/dungeon/f7/terms/TermStartTimer.java
+++ b/src/main/java/blade/addon/features/dungeon/f7/terms/TermStartTimer.java
@@ -34,6 +34,6 @@ public class TermStartTimer {
 
     public static void render(HUDComponent component, DrawContext context) {
         double num = tick * Constants.TICK_DURATION;
-        RenderUtils.drawCenteredText(context, component, Text.literal(Constants.DECIMAL_FORMAT.format(num)), Constants.YELLOW_COLOR);
+        RenderUtils.drawCenteredText(context, component, Text.literal(Constants.DECIMAL_FORMAT.format(num)), Constants.YELLOW);
     }
 }
diff --git a/src/main/java/blade/addon/features/filter/FilterEntry.java b/src/main/java/blade/addon/features/filter/FilterEntry.java
new file mode 100644
index 0000000..7482af2
--- /dev/null
+++ b/src/main/java/blade/addon/features/filter/FilterEntry.java
@@ -0,0 +1,156 @@
+package blade.addon.features.filter;
+
+import config.practical.utilities.Constants;
+import config.practical.utilities.DrawHelper;
+import net.minecraft.client.MinecraftClient;
+import net.minecraft.client.gl.RenderPipelines;
+import net.minecraft.client.gui.Click;
+import net.minecraft.client.gui.DrawContext;
+import net.minecraft.client.gui.widget.TextFieldWidget;
+import net.minecraft.client.input.CharInput;
+import net.minecraft.text.Text;
+import net.minecraft.util.Identifier;
+import net.minecraft.util.Pair;
+import net.minecraft.util.math.MathHelper;
+
+import java.util.function.Consumer;
+import java.util.function.Supplier;
+
+public class FilterEntry extends TextFieldWidget {
+
+    private static final Identifier CROSS = Identifier.of(blade.addon.utils.Constants.NAMESPACE, "cross");
+
+    private static final int HEIGHT = 20;
+    private static final int INPUT_COLOR = 0xff222222;
+
+    private static final int SPRITE_SIZE = 16;
+    private static final int SPRITE_WIDTH_AREA = SPRITE_SIZE + 10;
+    private int maxLength;
+    private int selectionEnd;
+    private Consumer<String> changedListener;
+
+    private final int index;
+    private final FilterList parent;
+
+    public FilterEntry(Supplier<String> supplier, Consumer<String> consumer, int index, FilterList parent) {
+        super(MinecraftClient.getInstance().textRenderer, Constants.WIDGET_WIDTH, HEIGHT, Text.empty());
+        this.setMaxLength(100);
+        this.setText(supplier.get());
+        this.setChangedListener(consumer);
+        this.index = index;
+        this.parent = parent;
+    }
+
+    @Override
+    public void renderWidget(DrawContext context, int mouseX, int mouseY, float deltaTicks) {
+        DrawHelper.drawBackground(context, getX(), super.getY(), width - SPRITE_WIDTH_AREA, height, INPUT_COLOR);
+        Pair<Integer, Integer> pos = getRemovePos();
+        context.drawGuiTexture(RenderPipelines.GUI_TEXTURED, CROSS, pos.getLeft(), pos.getRight(), SPRITE_SIZE, SPRITE_SIZE, 0xffffffff);
+        super.renderWidget(context, mouseX, mouseY, deltaTicks);
+    }
+
+    @Override
+    public boolean charTyped(CharInput input) {
+        if (!this.isActive()) {
+            return false;
+        } else if (input.isValidChar() || isValid(input)) {
+            this.write(input.asString());
+            return true;
+        } else {
+            return false;
+        }
+    }
+
+
+    private boolean isValid(CharInput input) {
+        return input.asString().contains("§");
+    }
+
+    /**
+     * A slightly modified version of the TextFieldWidget write(String)
+     * because it removes chars like §
+     *
+     * @param text The text to add
+     */
+    @Override
+    public void write(String text) {
+        int i = Math.min(getCursor(), this.selectionEnd);
+        int j = Math.max(getCursor(), this.selectionEnd);
+        int k = this.maxLength - getText().length() - (i - j);
+        if (k > 0) {
+            //String string = StringHelper.stripInvalidChars(text);
+            String string = text;
+            int l = string.length();
+            if (k < l) {
+                if (Character.isHighSurrogate(string.charAt(k - 1))) {
+                    k--;
+                }
+
+                string = string.substring(0, k);
+                l = k;
+            }
+            String string2 = new StringBuilder(getText()).replace(i, j, string).toString();
+            setText(string2);
+            this.setSelectionStart(i + l);
+            this.setSelectionEnd(getCursor());
+            changedListener.accept(getText());
+        }
+    }
+
+    @Override
+    public void setSelectionEnd(int index) {
+        this.selectionEnd = MathHelper.clamp(index, 0, getText().length());
+        super.setSelectionEnd(index);
+    }
+
+    @Override
+    public void setMaxLength(int maxLength) {
+        this.maxLength = maxLength;
+        super.setMaxLength(maxLength);
+    }
+
+    @Override
+    public void setChangedListener(Consumer<String> changedListener) {
+        this.changedListener = changedListener;
+        super.setChangedListener(changedListener);
+    }
+
+    @Override
+    public int getY() {
+        return super.getY();
+    }
+
+    @Override
+    public int getInnerWidth() {
+        return width - SPRITE_WIDTH_AREA - 8;
+    }
+
+    @Override
+    public boolean drawsBackground() {
+        return false;
+    }
+
+    private Pair<Integer, Integer> getRemovePos() {
+        return new Pair<>(getX() + width - SPRITE_SIZE - 5, getY() + (height - SPRITE_SIZE) / 2);
+    }
+
+    private boolean inRemovalBounds(double x, double y) {
+        Pair<Integer, Integer> pos = getRemovePos();
+        return x >= pos.getLeft() && x <=  pos.getLeft() + SPRITE_SIZE && y >= pos.getRight() && y <= pos.getRight() + SPRITE_SIZE;
+    }
+
+    @Override
+    public void onClick(Click click, boolean doubled) {
+        double x = click.x();
+        double y = click.y();
+
+        if (inRemovalBounds(x, y)) {
+            parent.removeFilter(index);
+            return;
+        }
+
+        //make sure you cant click around the button accidentally
+        if (width + getX() - SPRITE_WIDTH_AREA < x) return;
+        super.onClick(click, doubled);
+    }
+}
diff --git a/src/main/java/blade/addon/features/filter/FilterList.java b/src/main/java/blade/addon/features/filter/FilterList.java
new file mode 100644
index 0000000..5a187aa
--- /dev/null
+++ b/src/main/java/blade/addon/features/filter/FilterList.java
@@ -0,0 +1,93 @@
+package blade.addon.features.filter;
+
+import blade.addon.features.notifications.Notifications;
+import config.practical.ConfigScroll;
+import config.practical.utilities.Constants;
+import net.minecraft.client.MinecraftClient;
+import net.minecraft.client.gui.DrawContext;
+import net.minecraft.client.gui.screen.Screen;
+import net.minecraft.client.gui.widget.ButtonWidget;
+import net.minecraft.client.util.Window;
+import net.minecraft.text.Text;
+import org.joml.Matrix3x2fStack;
+
+public class FilterList extends Screen {
+
+    private static final int TITLE_COLOR = 0xffffffff;
+    private static final float TITLE_SCALAR = 1.5f;
+    private static final int TITLE_Y_OFFSET = 20;
+
+    private static final int BUTTON_HEIGHT = 30;
+
+    private final Screen parent;
+    private final ConfigScroll scroll;
+    private final ButtonWidget addFilter;
+
+    public FilterList() {
+        super(Text.literal("Filter"));
+
+        MinecraftClient client = MinecraftClient.getInstance();
+        parent = client.currentScreen;
+        Window window = client.getWindow();
+
+        addFilter = ButtonWidget.builder(Text.literal("Add Filter"), this::addFilter).position((window.getScaledWidth() - Constants.WIDGET_WIDTH) / 2,  TITLE_Y_OFFSET + 16).width(100).build();
+        scroll = new ConfigScroll(0,  BUTTON_HEIGHT + TITLE_Y_OFFSET + 16, window.getScaledWidth(), window.getScaledHeight() - BUTTON_HEIGHT, Constants.WIDGET_WIDTH);
+    }
+
+    @Override
+    public void render(DrawContext context, int mouseX, int mouseY, float deltaTicks) {
+        super.render(context, mouseX, mouseY, deltaTicks);
+
+        float centerX = (MinecraftClient.getInstance().getWindow().getScaledWidth() - (this.textRenderer.getWidth(this.title) * TITLE_SCALAR)) / 2;
+        Matrix3x2fStack stack = context.getMatrices();
+        stack.pushMatrix();
+        stack.translate(centerX, TITLE_Y_OFFSET);
+        stack.scale(TITLE_SCALAR, TITLE_SCALAR);
+        context.drawText(this.textRenderer, this.title, 0, 0, TITLE_COLOR, true);
+        stack.popMatrix();
+    }
+
+    @Override
+    protected void init() {
+        super.init();
+        this.addDrawableChild(scroll);
+        this.addDrawableChild(addFilter);
+        updateList();
+    }
+
+    private void updateList() {
+        scroll.children().clear();
+
+        for (int i = 0; i < Filters.filters.size(); i++) {
+            int finalI = i;
+            scroll.add(new FilterEntry(() -> Filters.filters.get(finalI), string -> {
+                //race condition check
+                if (finalI >= Filters.filters.size()) return;
+                Filters.filters.set(finalI, string);
+            }, finalI, this));
+        }
+
+        scroll.update();
+        scroll.setScrollY(0);
+    }
+
+    @Override
+    public void close() {
+        assert this.client != null;
+        Notifications.save();
+        this.client.setScreen(this.parent);
+        Filters.filterManager.save();
+    }
+
+    private void addFilter(ButtonWidget buttonWidget) {
+        Filters.filters.add("");
+        updateList();
+    }
+
+    public void removeFilter(int index) {
+        if (Filters.filters.size() <= index) return;
+        Filters.filters.remove(index);
+        updateList();
+    }
+
+}
diff --git a/src/main/java/blade/addon/features/filter/Filters.java b/src/main/java/blade/addon/features/filter/Filters.java
new file mode 100644
index 0000000..183466e
--- /dev/null
+++ b/src/main/java/blade/addon/features/filter/Filters.java
@@ -0,0 +1,31 @@
+package blade.addon.features.filter;
+
+import blade.addon.utils.config.FolderUtility;
+import config.practical.manager.ConfigManager;
+import config.practical.manager.ConfigValue;
+import net.fabricmc.fabric.api.client.message.v1.ClientReceiveMessageEvents;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public class Filters {
+
+    public static final ConfigManager filterManager = new ConfigManager(FolderUtility.CONFIG_PATH + FolderUtility.FILTERS_NAME,
+            List.of(Filters.class));
+
+    @ConfigValue
+    public static ArrayList<String> filters = new ArrayList<>();
+
+    public static void init() {
+        filterManager.load();
+        ClientReceiveMessageEvents.ALLOW_GAME.register((message, overlay) -> {
+            if (overlay) return true;
+            String string = message.getString();
+            for (String filter : filters) {
+                if (string.matches(filter)) return false;
+            }
+            return true;
+        });
+    }
+
+}
diff --git a/src/main/java/blade/addon/features/highlight/Highlight.java b/src/main/java/blade/addon/features/highlight/Highlight.java
deleted file mode 100644
index 865474b..0000000
--- a/src/main/java/blade/addon/features/highlight/Highlight.java
+++ /dev/null
@@ -1,4 +0,0 @@
-package blade.addon.features.highlight;
-
-public class Highlight {
-}
diff --git a/src/main/java/blade/addon/features/highlight/MimicHighlight.java b/src/main/java/blade/addon/features/highlight/MimicHighlight.java
index 2ff4580..3880a0f 100644
--- a/src/main/java/blade/addon/features/highlight/MimicHighlight.java
+++ b/src/main/java/blade/addon/features/highlight/MimicHighlight.java
@@ -43,14 +43,14 @@ public class MimicHighlight {
 
 
     private static void renderFilled(WorldRenderContext context, MatrixStack matrixStack, VertexConsumer consumer) {
-        if (!MobHighlight.mobHighlight || !MobHighlight.renderFilled()) return;
+        if (!MobHighlight.highlightMimicChests || !MobHighlight.renderFilled()) return;
 
         float[] rgba = RenderUtils.toFloats(MobHighlight.mimicFilledColor);
         mimics.forEach(mimic -> RenderUtils.renderFilled(matrixStack, consumer, box.offset(mimic.getPos()), rgba));
     }
 
     private static void renderOutline(WorldRenderContext context, MatrixStack matrixStack, VertexConsumer consumer) {
-        if (!MobHighlight.mobHighlight || !MobHighlight.renderOutline()) return;
+        if (!MobHighlight.highlightMimicChests || !MobHighlight.renderOutline()) return;
 
         float[] rgba = RenderUtils.toFloats(MobHighlight.mimicOutlineColor);
         mimics.forEach(mimic -> RenderUtils.renderOutline(matrixStack, consumer, box.offset(mimic.getPos()), rgba));
diff --git a/src/main/java/blade/addon/features/highlight/TeammateHighlight.java b/src/main/java/blade/addon/features/highlight/TeammateHighlight.java
index 60db950..e251712 100644
--- a/src/main/java/blade/addon/features/highlight/TeammateHighlight.java
+++ b/src/main/java/blade/addon/features/highlight/TeammateHighlight.java
@@ -62,7 +62,7 @@ public class TeammateHighlight {
             }
 
             if (Dungeons.renderClassName) {
-                Text text = Text.literal(player.getName().getString()).withColor(color).append(Text.literal(" [" + DungeonClass.getChar(clazz) + "]").withColor(Constants.YELLOW_COLOR));
+                Text text = Text.literal(player.getName().getString()).withColor(color).append(Text.literal(" [" + DungeonClass.getChar(clazz) + "]").withColor(Constants.YELLOW));
                 Vec3d pos = EntityUtil.getLerpedPos(player);
                 RenderUtils.renderText(context, matrixStack, text, pos.getX(), pos.getY() + 2.75, pos.getZ(), 2);
             }
diff --git a/src/main/java/blade/addon/features/notifications/NotificationEntry.java b/src/main/java/blade/addon/features/notifications/NotificationEntry.java
index 85c8149..bf4f958 100644
--- a/src/main/java/blade/addon/features/notifications/NotificationEntry.java
+++ b/src/main/java/blade/addon/features/notifications/NotificationEntry.java
@@ -28,7 +28,7 @@ class NotificationEntry extends ClickableWidget {
     private final Notification notification;
 
     public NotificationEntry(NotificationList parent, Notification notification) {
-        super(0, 0, Constants.WIDGET_WIDTH, 30, Text.empty());
+        super(0, 0, Constants.WIDGET_WIDTH, 28, Text.empty());
         this.parent = parent;
         this.notification = notification;
     }
diff --git a/src/main/java/blade/addon/features/notifications/Notifications.java b/src/main/java/blade/addon/features/notifications/Notifications.java
index fad8b49..8bcf74e 100644
--- a/src/main/java/blade/addon/features/notifications/Notifications.java
+++ b/src/main/java/blade/addon/features/notifications/Notifications.java
@@ -25,7 +25,7 @@ import java.util.concurrent.CopyOnWriteArrayList;
 public class Notifications {
 
     private static final String filePath = FolderUtility.CONFIG_PATH + FolderUtility.NOTIFICATIONS_NAME;
-    private static final String WAYPOINTS_NAME = "waypoints";
+    private static final String NOTIFICATIONS_NAME = "notifications";
 
     @ConfigValue
     public static CopyOnWriteArrayList<Notification> notifications;
@@ -82,7 +82,7 @@ public class Notifications {
         JsonObject obj = new JsonObject();
         Gson gson = new Gson();
 
-        obj.add(WAYPOINTS_NAME, gson.toJsonTree(notifications));
+        obj.add(NOTIFICATIONS_NAME, gson.toJsonTree(notifications));
 
         JsonElement tree = gson.toJsonTree(obj);
         try (FileWriter writer = new FileWriter(filePath)) {
@@ -101,7 +101,15 @@ public class Notifications {
         }
         Gson gson = new Gson();
         JsonObject object = gson.fromJson(jsonContent, JsonObject.class);
-        JsonElement element = object.get(WAYPOINTS_NAME);
+
+        JsonElement element;
+        if (object.has("waypoints")) {
+            //backwards compatibility because I forgot to change the name
+            //before release...
+            element = object.get("waypoints");
+        } else {
+            element = object.get(NOTIFICATIONS_NAME);
+        }
 
         if (!element.isJsonArray()) return;
         JsonArray array = element.getAsJsonArray();
diff --git a/src/main/java/blade/addon/features/other/ArrowSwapper.java b/src/main/java/blade/addon/features/other/ArrowSwapper.java
new file mode 100644
index 0000000..aa2fce2
--- /dev/null
+++ b/src/main/java/blade/addon/features/other/ArrowSwapper.java
@@ -0,0 +1,95 @@
+package blade.addon.features.other;
+
+import blade.addon.utils.config.values.ExtraOptions;
+import blade.addon.utils.data.ItemUtil;
+import blade.addon.utils.events.Events;
+import blade.addon.utils.rendering.RenderUtils;
+import config.practical.hud.HUDComponent;
+import net.fabricmc.fabric.api.client.event.lifecycle.v1.ClientTickEvents;
+import net.minecraft.client.gui.DrawContext;
+import net.minecraft.component.DataComponentTypes;
+import net.minecraft.component.type.LoreComponent;
+import net.minecraft.text.Text;
+import net.minecraft.util.Formatting;
+
+import java.util.List;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+public class ArrowSwapper {
+
+    private static final int TOTAL_TICK = 20;
+
+    private static final Pattern ARROW_PATTERN = Pattern.compile("^You set your selected arrow type to (.+)!$");
+    private static final Text NOT_SCANNED_TEXT = Text.literal("Cant find arrow").formatted(Formatting.RED);
+
+    private static Text displayedText = NOT_SCANNED_TEXT;
+    private static int tick = 0;
+
+    public static void init() {
+        ClientTickEvents.END_WORLD_TICK.register(world -> {
+            tick = Math.max(0, tick - 1);
+        });
+
+        Events.ON_GAME_MESSAGE.register(text -> {
+            String string = text.getString();
+
+            Matcher matcher = ARROW_PATTERN.matcher(string);
+            if (!matcher.find()) return false;
+
+            List<Text> siblings = text.getSiblings();
+            if (siblings.size() < 2) return false;
+            setDisplayedText(siblings.get(1));
+
+            return false;
+        });
+
+        Events.ON_SLOT_CHANGE.register((slot, item) -> {
+            if (displayedText != NOT_SCANNED_TEXT) return false;
+            if (!ItemUtil.itemHasName(item, "Arrow Swapper")) return false;
+
+            if (item ==null) return false;
+            LoreComponent lore = item.get(DataComponentTypes.LORE);
+            if (lore == null) return false;
+
+            List<Text> lines = lore.lines();
+            if (lines.isEmpty()) return false;
+
+            Pattern p = Pattern.compile("^Selected: (.+)$");
+
+            for (Text line : lines) {
+                Matcher matcher = p.matcher(line.getString());
+                if (matcher.find()) {
+                    List<Text> foundLine = line.getSiblings();
+                    if (foundLine.size() < 2) return false;
+                    setDisplayedText(foundLine.get(1));
+                    break;
+                }
+            }
+            return false;
+        });
+    }
+
+    private static void setDisplayedText(Text text) {
+        if (displayedText != NOT_SCANNED_TEXT) {
+            tick = TOTAL_TICK;
+        }
+        displayedText = text;
+    }
+
+    public static boolean display() {
+        return ExtraOptions.displayCurrentArrow;
+    }
+
+    public static void render(HUDComponent component, DrawContext context) {
+        RenderUtils.drawText(context, component, displayedText, 0xffffffff);
+    }
+
+    public static boolean displayNotification() {
+        return ExtraOptions.arrowSwapNotification && tick > 0;
+    }
+
+    public static void renderNotification(HUDComponent component, DrawContext context) {
+        RenderUtils.drawCenteredText(context, component, displayedText, 0xffffffff);
+    }
+}
diff --git a/src/main/java/blade/addon/features/other/PracticeSS.java b/src/main/java/blade/addon/features/other/PracticeSS.java
index de46bf1..c127203 100644
--- a/src/main/java/blade/addon/features/other/PracticeSS.java
+++ b/src/main/java/blade/
…… diff 过长已截断 ……
- added a toggle for key notification on all classes - added a toggle to instantly display place crystal reminder - added old bonzo staff sound - added time until quiz question
- fixed leap getting blocked in relics (ops) (needs testing) - fixed when transparency was 0 it still kind of rendered - fixed some colors being wrong - fixed the new mob highlight being bad, Thanks to ratph6 for the legendary tips
Gabriel Ellnebrand · 2026-01-25 · cbaa30d
查看变更
gradle.properties                                                                      |   2 +-
 src/main/java/blade/addon/Bladeaddons.java                                             |   5 ++
 src/main/java/blade/addon/features/dungeon/ChestCounter.java                           |   3 +-
 src/main/java/blade/addon/features/dungeon/KeyNotifier.java                            |  18 +++++--
 src/main/java/blade/addon/features/dungeon/QuizTimer.java                              |  54 +++++++++++++++++++++
 src/main/java/blade/addon/features/dungeon/WarpCooldown.java                           |   2 +-
 src/main/java/blade/addon/features/dungeon/f7/BossWaypoints.java                       |   6 ++-
 src/main/java/blade/addon/features/dungeon/f7/CrystalSpawn.java                        |   2 +-
 src/main/java/blade/addon/features/dungeon/f7/RelicTimer.java                          |  25 +++++++---
 src/main/java/blade/addon/features/dungeon/f7/invincibility/InvincibilityDuration.java |   6 ++-
 src/main/java/blade/addon/features/dungeon/f7/location/LocationNotifier.java           |   2 +-
 src/main/java/blade/addon/features/highlight/BatHighlight.java                         |   2 +-
 src/main/java/blade/addon/features/highlight/Highlight.java                            |   4 ++
 src/main/java/blade/addon/features/highlight/MimicHighlight.java                       |   2 +-
 src/main/java/blade/addon/features/highlight/MobHighlight.java                         | 166 +++++++++++++++++++++--------------------------------------------
 src/main/java/blade/addon/features/highlight/SheepHighlight.java                       |   2 +-
 src/main/java/blade/addon/features/highlight/WitherHighlight.java                      |   4 +-
 src/main/java/blade/addon/features/other/KickedTimer.java                              |   2 +-
 src/main/java/blade/addon/features/other/RagDisplay.java                               |   6 +--
 src/main/java/blade/addon/features/sound/BonzoSound.java                               |  29 ++++++++++++
 src/main/java/blade/addon/mixin/ArmorStandEntityMixin.java                             |  23 ---------
 src/main/java/blade/addon/mixin/ClientPlayNetworkHandlerMixin.java                     |   4 --
 src/main/java/blade/addon/mixin/ItemStackMixin.java                                    |  17 ++++++-
 src/main/java/blade/addon/utils/Misc.java                                              |   5 +-
 src/main/java/blade/addon/utils/config/Config.java                                     |  14 ++++--
 src/main/java/blade/addon/utils/config/components/Components.java                      |   4 ++
 src/main/java/blade/addon/utils/config/values/Dungeons.java                            |  12 +++--
 src/main/java/blade/addon/utils/config/values/ExtraOptions.java                        |   3 ++
 src/main/java/blade/addon/utils/config/values/Floor7.java                              |   3 ++
 src/main/java/blade/addon/utils/interfaces/ArmourStandHolder.java                      |  10 ----
 src/main/java/blade/addon/utils/rendering/RenderLayers.java                            |   1 -
 src/main/java/blade/addon/utils/rendering/RenderPipelines.java                         |   1 -
 src/main/java/blade/addon/utils/rendering/RenderUtils.java                             |  23 +++++++--
 src/main/java/blade/addon/utils/rendering/RenderingEvents.java                         |   4 +-
 src/main/resources/assets/blade-addons/sounds.json                                     |  11 +++++
 src/main/resources/assets/blade-addons/sounds/ghast-moan-1.ogg                         | Bin 0 -> 7986 bytes
 src/main/resources/assets/blade-addons/sounds/ghast-moan-2.ogg                         | Bin 0 -> 8909 bytes
 src/main/resources/assets/blade-addons/sounds/ghast-moan-3.ogg                         | Bin 0 -> 8925 bytes
 src/main/resources/assets/blade-addons/sounds/ghast-moan-4.ogg                         | Bin 0 -> 9393 bytes
 src/main/resources/assets/blade-addons/sounds/ghast-moan-5.ogg                         | Bin 0 -> 14107 bytes
 src/main/resources/assets/blade-addons/sounds/ghast-moan-6.ogg                         | Bin 0 -> 18305 bytes
 src/main/resources/assets/blade-addons/sounds/ghast-moan-7.ogg                         | Bin 0 -> 9222 bytes
 src/main/resources/assets/blade-addons/sounds/ghast-moan.ogg                           | Bin 0 -> 7986 bytes
 src/main/resources/blade-addons.mixins.json                                            |   1 -
 44 files changed, 281 insertions(+), 197 deletions(-)
diff --git a/gradle.properties b/gradle.properties
index e3549cb..7bb6dfd 100644
--- a/gradle.properties
+++ b/gradle.properties
@@ -13,7 +13,7 @@ loader_version=0.18.4
 loom_version=1.14-SNAPSHOT
 
 # Mod Properties
-mod_version=2.3.1
+mod_version=2.3.2
 maven_group=blade.addon
 archives_base_name=blade-addons
 
diff --git a/src/main/java/blade/addon/Bladeaddons.java b/src/main/java/blade/addon/Bladeaddons.java
index 10399dc..cea7b58 100644
--- a/src/main/java/blade/addon/Bladeaddons.java
+++ b/src/main/java/blade/addon/Bladeaddons.java
@@ -7,6 +7,7 @@ import blade.addon.features.dungeon.HidePlayers;
 import blade.addon.features.dungeon.ItemHighlight;
 import blade.addon.features.dungeon.KeyNotifier;
 import blade.addon.features.dungeon.LeapMessage;
+import blade.addon.features.dungeon.QuizTimer;
 import blade.addon.features.dungeon.RunStartValidator;
 import blade.addon.features.dungeon.SecretSpawnTimer;
 import blade.addon.features.dungeon.WarpCooldown;
@@ -42,6 +43,7 @@ import blade.addon.features.other.KickedTimer;
 import blade.addon.features.other.PracticeSS;
 import blade.addon.features.other.RagDisplay;
 import blade.addon.features.other.SelectedPet;
+import blade.addon.features.sound.BonzoSound;
 import blade.addon.utils.Commands;
 import blade.addon.utils.Keybinds;
 import blade.addon.utils.Location;
@@ -59,6 +61,7 @@ import blade.addon.utils.dungeon.Section;
 import blade.addon.utils.events.CustomEvents;
 import blade.addon.utils.rendering.RenderingEvents;
 import net.fabricmc.api.ModInitializer;
+import net.fabricmc.fabric.api.message.v1.ServerMessageEvents;
 
 public class Bladeaddons implements ModInitializer {
     @Override
@@ -124,5 +127,7 @@ public class Bladeaddons implements ModInitializer {
         InvincibilityDuration.init();
         TeammateHighlight.init();
         SectionProgress.init();
+        BonzoSound.init();
+        QuizTimer.init();
     }
 }
\ No newline at end of file
diff --git a/src/main/java/blade/addon/features/dungeon/ChestCounter.java b/src/main/java/blade/addon/features/dungeon/ChestCounter.java
index 27f9801..743a9ee 100644
--- a/src/main/java/blade/addon/features/dungeon/ChestCounter.java
+++ b/src/main/java/blade/addon/features/dungeon/ChestCounter.java
@@ -7,6 +7,7 @@ import blade.addon.utils.Scheduler;
 import blade.addon.utils.config.values.Dungeons;
 import blade.addon.utils.dungeon.Phase;
 import blade.addon.utils.events.Events;
+import blade.addon.utils.rendering.RenderUtils;
 import config.practical.hud.HUDComponent;
 import net.minecraft.client.MinecraftClient;
 import net.minecraft.client.gui.DrawContext;
@@ -71,7 +72,7 @@ public class ChestCounter {
         int y = component.getScaledY();
 
         if (hasUpdatedData) {
-            context.drawText(MinecraftClient.getInstance().textRenderer, Text.literal("Chests: " + Math.min(chestDisplayCount + countedChests, 60)), x, y, Dungeons.chestCountColor , true);
+            RenderUtils.drawPrefixedText(component, context, "Chests", Math.min(chestDisplayCount + countedChests, 60) + "");
         } else {
             context.drawText(MinecraftClient.getInstance().textRenderer, Text.literal("go to the dungeon hub"), x, y, Constants.RED_COLOR, true);
 
diff --git a/src/main/java/blade/addon/features/dungeon/KeyNotifier.java b/src/main/java/blade/addon/features/dungeon/KeyNotifier.java
index 3c1894b..6990ece 100644
--- a/src/main/java/blade/addon/features/dungeon/KeyNotifier.java
+++ b/src/main/java/blade/addon/features/dungeon/KeyNotifier.java
@@ -43,7 +43,6 @@ public class KeyNotifier {
     public static void init() {
         ClientTickEvents.END_WORLD_TICK.register((world) -> {
             if (!Location.inDungeon() || hasKey || !Dungeons.enableKeyNotifier) return;
-            if (!DungeonClass.isClass(DungeonClass.ARCHER) && !DungeonClass.isClass(DungeonClass.MAGE)) return;
 
             for (Entity entity : world.getEntities()) {
                 if (entity instanceof ArmorStandEntity armorStand && !foundKeys.contains(entity)) {
@@ -56,7 +55,9 @@ public class KeyNotifier {
                     if (uuid.equals(WITHER_UUID)) {
                         foundKeys.add(entity);
                         hasKey = true;
-                        Scheduler.scheduleSound(SoundEvents.ENTITY_EXPERIENCE_ORB_PICKUP, 2, 0.5f);
+                        if (isValidClass()) {
+                            Scheduler.scheduleSound(SoundEvents.ENTITY_EXPERIENCE_ORB_PICKUP, 2, 0.5f);
+                        }
                         return;
                     }
 
@@ -64,7 +65,10 @@ public class KeyNotifier {
                         foundKeys.add(entity);
                         isBloodKey = true;
                         hasKey = true;
-                        Scheduler.scheduleSound(SoundEvents.ENTITY_EXPERIENCE_ORB_PICKUP, 2, 0.5f);
+
+                        if (isValidClass()) {
+                            Scheduler.scheduleSound(SoundEvents.ENTITY_EXPERIENCE_ORB_PICKUP, 2, 0.5f);
+                        }
                         return;
                     }
                 }
@@ -73,7 +77,7 @@ public class KeyNotifier {
 
         Events.ON_GAME_MESSAGE.register(message -> {
             if (!Location.inDungeon() || !hasKey || !Dungeons.enableKeyNotifier) return false;
-            if (!DungeonClass.isClass(DungeonClass.ARCHER) && !DungeonClass.isClass(DungeonClass.MAGE)) return false;
+            if (!isValidClass()) return false;
 
             Matcher matcher = AUTOMATIC_PICKUP_PATTERN.matcher(message.getString());
             if (matcher.find()) {
@@ -101,8 +105,12 @@ public class KeyNotifier {
         });
     }
 
+    private static boolean isValidClass() {
+       return Dungeons.displayKeyForAllClasses || DungeonClass.isClass(DungeonClass.ARCHER) || DungeonClass.isClass(DungeonClass.MAGE);
+    }
+
     public static boolean display() {
-        return Location.inDungeon() && hasKey;
+        return Location.inDungeon() && hasKey && isValidClass();
     }
 
     public static void render(HUDComponent component, DrawContext context) {
diff --git a/src/main/java/blade/addon/features/dungeon/QuizTimer.java b/src/main/java/blade/addon/features/dungeon/QuizTimer.java
new file mode 100644
index 0000000..40cf5d5
--- /dev/null
+++ b/src/main/java/blade/addon/features/dungeon/QuizTimer.java
@@ -0,0 +1,54 @@
+package blade.addon.features.dungeon;
+
+import blade.addon.utils.Location;
+import blade.addon.utils.config.values.Dungeons;
+import blade.addon.utils.events.Events;
+import blade.addon.utils.rendering.RenderUtils;
+import config.practical.hud.HUDComponent;
+import net.minecraft.client.gui.DrawContext;
+
+public class QuizTimer {
+
+    private static final int START_DURATION = 220;
+    private static final int QUESTION_DURATION = 100;
+
+    private static int tick = 0;
+
+    public static void init() {
+
+        Events.ON_GAME_MESSAGE.register(text -> {
+            if (!Location.inDungeon()) return false;
+
+            String string = text.getString();
+            if (string.equals("[STATUE] Oruo the Omniscient: I am Oruo the Omniscient. I have lived many lives. I have learned all there is to know.")) {
+                tick = START_DURATION;
+            } else if (string.equals("[STATUE] Oruo the Omniscient: 2 questions left... Then you will have proven your worth to me!")) {
+                tick = QUESTION_DURATION;
+            } else if (string.equals("[STATUE] Oruo the Omniscient: One more question!")) {
+                tick = QUESTION_DURATION;
+            }
+
+            return false;
+        });
+
+        Events.ON_SERVER_TICK.register(() -> {
+            tick = Math.max(tick - 1, 0);
+            return false;
+        });
+
+        Events.ON_LOCATION_CHANGE.register(newLocation -> {
+            tick = 0;
+            return false;
+        });
+    }
+
+    public static boolean display() {
+        return Dungeons.quizTimer && tick > 0;
+    }
+
+    public static void render(HUDComponent component, DrawContext context) {
+       RenderUtils.drawPrefixedTimer(component, context, "Quiz", tick);
+    }
+
+
+}
diff --git a/src/main/java/blade/addon/features/dungeon/WarpCooldown.java b/src/main/java/blade/addon/features/dungeon/WarpCooldown.java
index 010f5db..5f6829f 100644
--- a/src/main/java/blade/addon/features/dungeon/WarpCooldown.java
+++ b/src/main/java/blade/addon/features/dungeon/WarpCooldown.java
@@ -42,6 +42,6 @@ public class WarpCooldown {
 
     public static void render(HUDComponent component, DrawContext context) {
         double timeRemaining = Math.max((endTime - System.currentTimeMillis()) / 1000.0, 0);
-        RenderUtils.drawPrefixedTimer(component, context, timeRemaining, "Warp Cooldown");
+        RenderUtils.drawPrefixedTimer(component, context, "Warp Cooldown", timeRemaining);
     }
 }
diff --git a/src/main/java/blade/addon/features/dungeon/f7/BossWaypoints.java b/src/main/java/blade/addon/features/dungeon/f7/BossWaypoints.java
index 5e9aee9..61047c4 100644
--- a/src/main/java/blade/addon/features/dungeon/f7/BossWaypoints.java
+++ b/src/main/java/blade/addon/features/dungeon/f7/BossWaypoints.java
@@ -60,6 +60,10 @@ public class BossWaypoints {
         return place;
     }
 
+    public static boolean getIgnoreBoss() {
+        return ignoreBoss;
+    }
+
     public static void setPlace(boolean shouldPlace) {
         place = shouldPlace;
         Misc.addChatMessage(Text.literal("Edit mode: ").append(Misc.getStatusText(place)));
@@ -85,7 +89,7 @@ public class BossWaypoints {
 
     private static void addWaypoint(double x, double y, double z, double dx, double dy, double dz) {
         float[] colorAsFloat = RenderUtils.toFloats(Floor7.nextWaypointColor);
-        waypoints.add(new Waypoint(x, y, z, dx, dy, dz, colorAsFloat[0], colorAsFloat[1], colorAsFloat[2], colorAsFloat[3],Floor7.nextWaypointThroughWall));
+        waypoints.add(new Waypoint(x, y, z, dx, dy, dz, colorAsFloat[0], colorAsFloat[1], colorAsFloat[2], colorAsFloat[3], Floor7.nextWaypointThroughWall));
         save();
     }
 
diff --git a/src/main/java/blade/addon/features/dungeon/f7/CrystalSpawn.java b/src/main/java/blade/addon/features/dungeon/f7/CrystalSpawn.java
index ff4cd97..3c36a48 100644
--- a/src/main/java/blade/addon/features/dungeon/f7/CrystalSpawn.java
+++ b/src/main/java/blade/addon/features/dungeon/f7/CrystalSpawn.java
@@ -104,7 +104,7 @@ public class CrystalSpawn {
     }
 
     public static boolean displayNotification() {
-        return tickSincePicked > REMINDER_TICK && Location.inDungeon() && Phase.inP1() && Floor7.crystalPlaceReminder && pickedUp;
+        return (Floor7.instantlyDisplayCrystalReminder || tickSincePicked > REMINDER_TICK) && Location.inDungeon() && Phase.inP1() && Floor7.crystalPlaceReminder && pickedUp;
     }
 
     public static void renderNotification(HUDComponent component, DrawContext context) {
diff --git a/src/main/java/blade/addon/features/dungeon/f7/RelicTimer.java b/src/main/java/blade/addon/features/dungeon/f7/RelicTimer.java
index 86cca09..8fbf1fc 100644
--- a/src/main/java/blade/addon/features/dungeon/f7/RelicTimer.java
+++ b/src/main/java/blade/addon/features/dungeon/f7/RelicTimer.java
@@ -151,6 +151,8 @@ public class RelicTimer {
 
             BlockPos pos = result.getBlockPos();
 
+            if (!isARelicPos(pos)) return false;
+
             //skyblock menu check is there incase the item doesnt get updated
             if (!ItemUtil.itemHasName(itemStack, "Relic") && !ItemUtil.itemHasName(itemStack, "Skyblock Menu")) {
                 if (Floor7.blockIncorrectRelicPlace) {
@@ -162,7 +164,7 @@ public class RelicTimer {
                 }
             }
 
-            if (pos.getX() == pickedupRelic.box.minX && (pos.getY() == pickedupRelic.box.minY || pos.getY() == pickedupRelic.box.minY - 1) && pos.getZ() == pickedupRelic.box.minZ) {
+            if (clickedRelic(pickedupRelic, pos)) {
                 if (Floor7.enableRelicPlaceTime) {
                     MutableText text = Text.literal("The ").formatted(Formatting.GREEN)
                             .append(Text.literal(pickedupRelic.name().toLowerCase()).withColor(pickedupRelic.color))
@@ -192,6 +194,22 @@ public class RelicTimer {
 
     }
 
+    private static boolean isARelicPos(BlockPos pos) {
+        try {
+            for (Relic relic : Relic.values()) {
+                if (clickedRelic(relic, pos)) return true;
+            }
+        } catch (Exception e) {
+            Misc.addChatMessage(Text.literal("Relic not found!"));
+        }
+        return false;
+    }
+
+    private static boolean clickedRelic(Relic relic, BlockPos pos) {
+        if (relic == null) return false;
+        return (pos.getX() == relic.box.minX && (pos.getY() == relic.box.minY || pos.getY() == relic.box.minY - 1) && pos.getZ() == relic.box.minZ);
+    }
+
     private static void reset() {
         tick = Floor7.relicSpawnTicks;
         pickedupRelic = null;
@@ -225,11 +243,6 @@ public class RelicTimer {
 
     public static void printRelic() {
         Misc.addChatMessage(Text.literal("Relic: " + pickedupRelic));
-
-    }
-
-    public static void debugCommands() {
-
     }
 
     private static void worldRender(WorldRenderContext context, MatrixStack matrixStack, VertexConsumer consumer) {
diff --git a/src/main/java/blade/addon/features/dungeon/f7/invincibility/InvincibilityDuration.java b/src/main/java/blade/addon/features/dungeon/f7/invincibility/InvincibilityDuration.java
index 254260d..37146b8 100644
--- a/src/main/java/blade/addon/features/dungeon/f7/invincibility/InvincibilityDuration.java
+++ b/src/main/java/blade/addon/features/dungeon/f7/invincibility/InvincibilityDuration.java
@@ -27,6 +27,10 @@ public class InvincibilityDuration {
     }
 
     public static void render(HUDComponent component, DrawContext context) {
-        RenderUtils.drawTimer(component, context, ticks, 0xffffffff);
+        int color = 0xffffffff;
+        if (Dungeons.useStatusColorForInvincibility) {
+            color = RenderUtils.getStatusColor(40, 20, ticks);
+        }
+        RenderUtils.drawTimer(component, context, ticks, color);
     }
 }
diff --git a/src/main/java/blade/addon/features/dungeon/f7/location/LocationNotifier.java b/src/main/java/blade/addon/features/dungeon/f7/location/LocationNotifier.java
index 4e18af2..c7da250 100644
--- a/src/main/java/blade/addon/features/dungeon/f7/location/LocationNotifier.java
+++ b/src/main/java/blade/addon/features/dungeon/f7/location/LocationNotifier.java
@@ -49,7 +49,7 @@ public class LocationNotifier {
             color = DungeonClass.getColor(username);
         }
 
-        notification = Text.literal(username).withColor(color).append(Text.literal("§6" + message).setStyle(Style.EMPTY));
+        notification = Text.literal(username).withColor(color).append(Text.literal("§e" + message).setStyle(Style.EMPTY));
         ticks = Floor7.notificationDuration;
 
         for (int i = 0; i < Floor7.notificationRepetitions; i++) {
diff --git a/src/main/java/blade/addon/features/highlight/BatHighlight.java b/src/main/java/blade/addon/features/highlight/BatHighlight.java
index da8f986..12b72dc 100644
--- a/src/main/java/blade/addon/features/highlight/BatHighlight.java
+++ b/src/main/java/blade/addon/features/highlight/BatHighlight.java
@@ -23,7 +23,7 @@ public class BatHighlight {
 
         Events.ON_ENTITY_TRACKED.register((entity, world) -> {
             if (!Location.inDungeon() || !MobHighlight.mobHighlight) return false;
-            if (entity instanceof BatEntity bat) {
+            if (entity instanceof BatEntity bat && !bats.contains(bat)) {
                 for (float health : BAT_HEALTHS) {
                     if (health == bat.getHealth()) {
                         bats.add(bat);
diff --git a/src/main/java/blade/addon/features/highlight/Highlight.java b/src/main/java/blade/addon/features/highlight/Highlight.java
new file mode 100644
index 0000000..865474b
--- /dev/null
+++ b/src/main/java/blade/addon/features/highlight/Highlight.java
@@ -0,0 +1,4 @@
+package blade.addon.features.highlight;
+
+public class Highlight {
+}
diff --git a/src/main/java/blade/addon/features/highlight/MimicHighlight.java b/src/main/java/blade/addon/features/highlight/MimicHighlight.java
index 5275f88..2ff4580 100644
--- a/src/main/java/blade/addon/features/highlight/MimicHighlight.java
+++ b/src/main/java/blade/addon/features/highlight/MimicHighlight.java
@@ -29,7 +29,7 @@ public class MimicHighlight {
 
        Events.ON_BLOCK_ENTITY.register(blockEntity -> {
           if (!Location.inDungeon()) return false;
-          if (blockEntity instanceof TrappedChestBlockEntity mimic) {
+          if (blockEntity instanceof TrappedChestBlockEntity mimic && !mimics.contains(mimic)) {
               mimics.add(mimic);
           }
            return false;
diff --git a/src/main/java/blade/addon/features/highlight/MobHighlight.java b/src/main/java/blade/addon/features/highlight/MobHighlight.java
index e87bb3d..54fbbb3 100644
--- a/src/main/java/blade/addon/features/highlight/MobHighlight.java
+++ b/src/main/java/blade/addon/features/highlight/MobHighlight.java
@@ -3,23 +3,19 @@ package blade.addon.features.highlight;
 import blade.addon.utils.Location;
 import blade.addon.utils.data.EntityUtil;
 import blade.addon.utils.events.Events;
-import blade.addon.utils.interfaces.ArmourStandHolder;
 import blade.addon.utils.rendering.RenderUtils;
 import blade.addon.utils.rendering.RenderingEvents;
 import config.practical.manager.ConfigValue;
+import net.fabricmc.fabric.api.client.event.lifecycle.v1.ClientEntityEvents;
 import net.fabricmc.fabric.api.client.event.lifecycle.v1.ClientTickEvents;
 import net.fabricmc.fabric.api.client.rendering.v1.world.WorldRenderContext;
-import net.minecraft.client.MinecraftClient;
 import net.minecraft.client.network.ClientPlayerEntity;
 import net.minecraft.client.render.VertexConsumer;
 import net.minecraft.client.util.math.MatrixStack;
-import net.minecraft.client.world.ClientWorld;
 import net.minecraft.entity.Entity;
 import net.minecraft.entity.decoration.ArmorStandEntity;
 import net.minecraft.entity.effect.StatusEffects;
 import net.minecraft.entity.mob.EndermanEntity;
-import net.minecraft.entity.mob.SkeletonEntity;
-import net.minecraft.entity.mob.WitherSkeletonEntity;
 import net.minecraft.entity.mob.ZombieEntity;
 import net.minecraft.entity.player.PlayerEntity;
 import net.minecraft.item.Item;
@@ -29,10 +25,8 @@ import net.minecraft.text.Text;
 import net.minecraft.text.TextColor;
 import net.minecraft.util.math.Box;
 
-import java.util.ArrayList;
 import java.util.concurrent.ConcurrentHashMap;
 import java.util.concurrent.ConcurrentLinkedQueue;
-import java.util.function.Predicate;
 
 public class MobHighlight {
 
@@ -40,7 +34,8 @@ public class MobHighlight {
         STAR, TANK, MINI, FEL, ASSASSIN, MIMIC
     }
 
-    record DataHolder(Entity entity, MobHighlight.MobType type) {}
+    public record DataHolder(Entity entity, MobHighlight.MobType type) {
+    }
 
     public enum HighlightType {
         FILLED("Filled"), OUTLINE("Outline"), BOTH("Both");
@@ -57,11 +52,10 @@ public class MobHighlight {
         }
     }
 
-    private static final int DIAMOND_HELMET_ID = Item.getRawId(Items.DIAMOND_HELMET);
-    private static final int PLAYER_HEAD_ID = Item.getRawId(Items.PLAYER_HEAD);
     private static final int LEATHER_BOOTS_ID = Item.getRawId(Items.LEATHER_BOOTS);
 
-    private static final ConcurrentLinkedQueue<DataHolder> savedEntities = new ConcurrentLinkedQueue<>();
+    private static final ConcurrentHashMap<Integer, Entity> foundEntities = new ConcurrentHashMap<>();
+    public static final ConcurrentLinkedQueue<DataHolder> savedEntities = new ConcurrentLinkedQueue<>();
 
     public static boolean dontRenderHighlight = false;
     @ConfigValue
@@ -121,22 +115,12 @@ public class MobHighlight {
     public static int sheepOutlineColor = 0xffffffff;
 
 
-
     public static void init() {
-        ClientTickEvents.END_CLIENT_TICK.register(client -> {
-            if (!Location.inDungeon()) return;
-
-            ClientWorld world = client.world;
-            if (world == null) return;
-            for (Entity entity: world.getEntities()) {
-                if (entity instanceof ArmorStandEntity armorStand) {
-                    ArmourStandHolder holder = (ArmourStandHolder) armorStand;
-                    if (holder.blade_addons$hasBeenScanned()) continue;
-                    holder.blade_addons$setScanned(true);
-                    testArmourStand(armorStand, client);
-                }
-            }
-            savedEntities.removeIf(dataHolder -> dataHolder.entity.isRemoved());
+
+        Events.ON_ENTITY_SPAWNED.register((entity, world) -> {
+            if (entity instanceof ArmorStandEntity) return false;
+            foundEntities.put(entity.getId(), entity);
+            return false;
         });
 
         Events.ON_ENTITY_TRACKED.register((entity, world) -> {
@@ -148,6 +132,10 @@ public class MobHighlight {
                 }
             }
 
+            if (entity instanceof ArmorStandEntity armorStand) {
+                testArmorStand(armorStand);
+            }
+
             return false;
         });
 
@@ -162,35 +150,51 @@ public class MobHighlight {
             dontRenderHighlight = player.hasStatusEffect(StatusEffects.BLINDNESS);
         });
 
+        ClientEntityEvents.ENTITY_UNLOAD.register((entity, world) -> {
+            if (entity == null) return;
+            int id = entity.getId();
+            savedEntities.removeIf(dataHolder -> dataHolder.entity == entity);
+            foundEntities.remove(id);
+        });
+
         RenderingEvents.FILLED_ENTITY.register(MobHighlight::renderFilled);
         RenderingEvents.OUTLINE_ENTITY.register(MobHighlight::renderOutline);
     }
 
-    public static void testArmourStand(ArmorStandEntity armorStand, MinecraftClient client) {
-        Text text = armorStand.getCustomName();
-        if (text == null) return;
-        String name = text.getString();
+    private static void testArmorStand(ArmorStandEntity armorStand) {
+        MobType type = getType(armorStand);
+        if (type == null) return;
 
-        ClientWorld world = client.world;
-        if (world == null) return;
+        int idOffset = getIdOffset(armorStand);
+        if (idOffset < 0) return;
 
-        if (name.contains("Mimic")) {
-            attemptAddMob(armorStand, MobHighlight::isAPossibleMimic, 3.5, MobHighlight.MobType.MIMIC, world);
-            return;
-        }
+        int id = armorStand.getId() - idOffset;
+        Entity entity = foundEntities.getOrDefault(id, null);
+        if (entity == null) return;
+        foundEntities.remove(id);
+        savedEntities.add(new DataHolder(entity, type));
+    }
 
-        if (!MobHighlight.containsStarText(text)) return;
+    public static MobType getType(ArmorStandEntity armorStand) {
+        Text text = armorStand.getCustomName();
+        if (text == null) return null;
+        String name = text.getString();
+        if (name.contains("King Midas")) return MobType.MINI;
+        if (name.contains("Mimic")) return MobType.MIMIC;
+        if (!MobHighlight.containsStarText(text)) return null;
+        if (name.contains("Fel")) return MobType.FEL;
+        if (isMiniBoss(name)) return MobType.MINI;
+        if (isTankMob(name)) return MobType.TANK;
+        return MobType.STAR;
+    }
 
-        if (name.contains("Fel")) {
-            attemptAddMob(armorStand, entity -> entity instanceof EndermanEntity, 4, MobHighlight.MobType.FEL, world);
-        } else if (isMiniBoss(name)) {
-            attemptAddMob(armorStand, MobHighlight::isMiniBoss, 4, MobHighlight.MobType.MINI, world);
-        } else if (isTankMob(name)) {
-            attemptAddMob(armorStand, MobHighlight::isAPossibleStaredMob, 3, MobHighlight.MobType.TANK, world);
-        } else {
-            attemptAddMob(armorStand, MobHighlight::isAPossibleStaredMob, 3, MobHighlight.MobType.STAR, world);
-        }
+    public static int getIdOffset(ArmorStandEntity armorStand) {
+        Text text = armorStand.getCustomName();
+        if (text == null) return -1;
+        String name = text.getString();
 
+        if (name.toLowerCase().contains("withermancer")) return 3;
+        return 1;
     }
 
     public static boolean containsStarText(Text text) {
@@ -201,58 +205,19 @@ public class MobHighlight {
             if (color.getRgb() == 0xFFAA00 && sib.getString().equals("✯ ")) {
                 return true;
             }
-
         }
 
         return false;
     }
 
     public static boolean isTankMob(String name) {
-        return name.contains("Zombie Commander") || name.contains("Zombie Lord") || name.contains("Skeleton Lord")|| name.contains("Withermancer") || name.contains("Super Archer");
+        return name.contains("Zombie Commander") || name.contains("Zombie Lord") || name.contains("Skeleton Lord") || name.contains("Withermancer") || name.contains("Super Archer");
     }
 
     public static boolean isMiniBoss(String name) {
         return name.contains("Lost Adventurer") || name.contains("Angry Archaeologist") || name.contains("Frozen Adventurer");
     }
 
-    public static void attemptAddMob(ArmorStandEntity armorStand, Predicate<Entity> requirements, double maxDistance, MobHighlight.MobType mobType, ClientWorld world) {
-        Entity closest = null;
-        double smallestDistance = maxDistance;
-        double maxY = armorStand.getY();
-        Box boundingBox = Box.of(armorStand.getEntityPos().subtract(0, 0.5, 0), 2, 3, 2);
-
-
-        ArrayList<Entity> meetsRequirements = new ArrayList<>();
-
-        Iterable<Entity> entities = world.getEntities();
-        entities.forEach(entity -> {
-            if (!entity.getBoundingBox().intersects(boundingBox)) return;
-            if (!requirements.test(entity)) return;
-            if (entity.getY() > maxY) return;
-
-            meetsRequirements.add(entity);
-        });
-
-        for (Entity current : meetsRequirements) {
-            if (!current.getBoundingBox().intersects(boundingBox)) continue;
-            if (!requirements.test(current)) continue;
-            if (current.getY() > maxY) continue;
-
-            double distance = armorStand.getEntityPos().distanceTo(current.getEntityPos());
-            if (smallestDistance > distance) {
-                closest = current;
-                smallestDistance = distance;
-            }
-
-        }
-
-        if (closest == null) {
-            return;
-        }
-
-        savedEntities.add(new DataHolder(closest, mobType));
-    }
-
     public static boolean isShadowAssassin(PlayerEntity player) {
         if (EntityUtil.isARealPlayer(player)) return false;
         ItemStack heldItem = player.getMainHandStack();
@@ -265,34 +230,6 @@ public class MobHighlight {
         return Item.getRawId(boots.getItem()) == LEATHER_BOOTS_ID;
     }
 
-    public static boolean isAPossibleStaredMob(Entity entity) {
-        return switch (entity) {
-            case ZombieEntity ignored -> true;
-            case SkeletonEntity ignored -> true;
-            case WitherSkeletonEntity ignored -> true;
-            case PlayerEntity ignored -> !EntityUtil.isARealPlayer(entity) && !EntityUtil.isClientPlayer(entity) && !isMiniBoss(entity);
-            default -> false;
-        };
-    }
-
-    public static boolean isAPossibleMimic(Entity entity) {
-        if (entity instanceof ZombieEntity zombie) {
-            return zombie.isBaby();
-        }
-        return false;
-    }
-
-    public static boolean isMiniBoss(Entity entity) {
-        if (entity instanceof PlayerEntity player) {
-            if (EntityUtil.isARealPlayer(player)) return false;
-            ItemStack helmet = player.getInventory().getStack(39);
-            int id = Item.getRawId(helmet.getItem());
-            return id == DIAMOND_HELMET_ID || id == PLAYER_HEAD_ID;
-        }
-
-        return false;
-    }
-
     public static int getFilledColor(MobType mob) {
         return switch (mob) {
             case STAR -> starFilledColor;
@@ -342,6 +279,7 @@ public class MobHighlight {
         return box;
     }
 
+
     private static void renderFilled(WorldRenderContext context, MatrixStack matrixStack, VertexConsumer consumer) {
         if (!MobHighlight.mobHighlight || dontRenderHighlight || !MobHighlight.renderFilled()) return;
 
diff --git a/src/main/java/blade/addon/features/highlight/SheepHighlight.java b/src/main/java/blade/addon/features/highlight/SheepHighlight.java
index 0e3121f..cadf740 100644
--- a/src/main/java/blade/addon/features/highlight/SheepHighlight.java
+++ b/src/main/java/blade/addon/features/highlight/SheepHighlight.java
@@ -21,7 +21,7 @@ public class SheepHighlight {
 
         Events.ON_ENTITY_SPAWNED.register((entity, world) -> {
             if (!Location.inDungeon()) return false;
-            if (entity instanceof SheepEntity sheep) {
+            if (entity instanceof SheepEntity sheep && !sheeps.contains(sheep)) {
                 sheeps.add(sheep);
             }
             return false;
diff --git a/src/main/java/blade/addon/features/highlight/WitherHighlight.java b/src/main/java/blade/addon/features/highlight/WitherHighlight.java
index 0f4d454..01cfb1c 100644
--- a/src/main/java/blade/addon/features/highlight/WitherHighlight.java
+++ b/src/main/java/blade/addon/features/highlight/WitherHighlight.java
@@ -28,7 +28,7 @@ public class WitherHighlight {
 
             if (Phase.inBoss()) {
                 if (entity instanceof WitherEntity wither) {
-                    if (wither.getHealth() != WITHER_BORN_HEALTH) {
+                    if (wither.getHealth() != WITHER_BORN_HEALTH && !withers.contains(wither)) {
                         withers.add(wither);
                     }
                 }
@@ -42,7 +42,7 @@ public class WitherHighlight {
             return false;
         });
 
-        ClientTickEvents.END_CLIENT_TICK.register(client -> withers.removeIf(wither -> wither.isRemoved() || wither.isDead()));
+        ClientTickEvents.END_CLIENT_TICK.register(client -> {withers.removeIf(wither -> wither.isRemoved() || wither.isDead());});
 
         RenderingEvents.FILLED_ENTITY.register(WitherHighlight::renderFilled);
         RenderingEvents.OUTLINE_ENTITY.register(WitherHighlight::renderOutline);
diff --git a/src/main/java/blade/addon/features/other/KickedTimer.java b/src/main/java/blade/addon/features/other/KickedTimer.java
index 5aaf47c..dec773a 100644
--- a/src/main/java/blade/addon/features/other/KickedTimer.java
+++ b/src/main/java/blade/addon/features/other/KickedTimer.java
@@ -34,7 +34,7 @@ public class KickedTimer {
         long diff = System.currentTimeMillis() - kickedTime;
         if (diff > 60 * 1000) isKicked = false;
         double drawnTime = Math.min(diff / 1000.0, 60.0);
-        RenderUtils.drawPrefixedTimer(component, context, drawnTime, "Time kicked");
+        RenderUtils.drawPrefixedTimer(component, context, "Time kicked", drawnTime);
     }
 
 }
diff --git a/src/main/java/blade/addon/features/other/RagDisplay.java b/src/main/java/blade/addon/features/other/RagDisplay.java
index 94074a7..54c6b91 100644
--- a/src/main/java/blade/addon/features/other/RagDisplay.java
+++ b/src/main/java/blade/addon/features/other/RagDisplay.java
@@ -1,7 +1,7 @@
 package blade.addon.features.other;
 
 import blade.addon.utils.Constants;
-import blade.addon.utils.Scheduler;
+import blade.addon.utils.Misc;
 import blade.addon.utils.config.values.ExtraOptions;
 import blade.addon.utils.data.ItemUtil;
 import blade.addon.utils.events.Events;
@@ -34,10 +34,10 @@ public class RagDisplay {
             }
 
             if (ExtraOptions.useOldRagSound) {
-                Scheduler.scheduleSound(OLD_RAG_SOUND, 1, 1.4920635223388672f);
+                Misc.sendSound(OLD_RAG_SOUND, volume, pitch);
                 return true;
             } else if (ExtraOptions.useCustomRagSound) {
-                Scheduler.scheduleSound(ExtraOptions.ragSound);
+                Misc.sendSound(ExtraOptions.ragSound);
                 return true;
             }
             return false;
diff --git a/src/main/java/blade/addon/features/sound/BonzoSound.java b/src/main/java/blade/addon/features/sound/BonzoSound.java
new file mode 100644
index 0000000..8d2a09e
--- /dev/null
+++ b/src/main/java/blade/addon/features/sound/BonzoSound.java
@@ -0,0 +1,29 @@
+package blade.addon.features.sound;
+
+import blade.addon.utils.Constants;
+import blade.addon.utils.Misc;
+import blade.addon.utils.config.values.ExtraOptions;
+import blade.addon.utils.events.Events;
+import net.minecraft.sound.SoundEvent;
+import net.minecraft.sound.SoundEvents;
+import net.minecraft.util.Identifier;
+
+public class BonzoSound {
+
+    private static final SoundEvent OLD_BONZO_SOUND = SoundEvent.of(Identifier.of(Constants.NAMESPACE, "ghast-moan"));
+
+
+    public static void init() {
+        Events.ON_SOUND.register((soundEvent, volume, pitch) -> {
+            if (volume != 1.0 || soundEvent != SoundEvents.ENTITY_GHAST_WARN) return false;
+
+            if (ExtraOptions.oldBonzoSound) {
+                Misc.sendSound(OLD_BONZO_SOUND, volume, pitch);
+                return true;
+            }
+
+            return ExtraOptions.disableBonzoSound;
+        });
+    }
+
+}
diff --git a/src/main/java/blade/addon/mixin/ArmorStandEntityMixin.java b/src/main/java/blade/addon/mixin/ArmorStandEntityMixin.java
deleted file mode 100644
index 13ad612..0000000
--- a/src/main/java/blade/addon/mixin/ArmorStandEntityMixin.java
+++ /dev/null
@@ -1,23 +0,0 @@
-package blade.addon.mixin;
-
-import blade.addon.utils.interfaces.ArmourStandHolder;
-import net.minecraft.entity.decoration.ArmorStandEntity;
-import org.spongepowered.asm.mixin.Mixin;
-import org.spongepowered.asm.mixin.Unique;
-
-@Mixin(ArmorStandEntity.class)
-public class ArmorStandEntityMixin implements ArmourStandHolder {
-
-    @Unique
-    private boolean scanned = false;
-
-    @Override
-    public boolean blade_addons$hasBeenScanned() {
-        return scanned;
-    }
-
-    @Override
-    public void blade_addons$setScanned(boolean value) {
-        scanned = value;
-    }
-}
diff --git a/src/main/java/blade/addon/mixin/ClientPlayNetworkHandlerMixin.java b/src/main/java/blade/addon/mixin/ClientPlayNetworkHandlerMixin.java
index edb13ac..748667f 100644
--- a/src/main/java/blade/addon/mixin/ClientPlayNetworkHandlerMixin.java
+++ b/src/main/java/blade/addon/mixin/ClientPlayNetworkHandlerMixin.java
@@ -69,10 +69,6 @@ public class ClientPlayNetworkHandlerMixin {
             ci.cancel();
         }
 
-        if (ExtraOptions.disableBonzoSound && volume == 1.0 && event == SoundEvents.ENTITY_GHAST_WARN) {
-            ci.cancel();
-        }
-
         if (Debug.sendSound) {
             Misc.addChatMessage(Text.literal("Sound: " + event.id() + " Volume: " + volume + " Pitch: " + pitch));
         }
diff --git a/src/main/java/blade/addon/mixin/ItemStackMixin.java b/src/main/java/blade/addon/mixin/ItemStackMixin.java
index 61d84b2..2b19cd3 100644
--- a/src/main/java/blade/addon/mixin/ItemStackMixin.java
+++ b/src/main/java/blade/addon/mixin/ItemStackMixin.java
@@ -38,85 +38,100 @@ public class ItemStackMixin implements ItemRarityHolder, PetHolder, StarCountHol
     @Unique
     MaskType maskType = null;
 
+    @Unique
     @Override
     public ItemRarity blade_addons$getItemRarity() {
         return itemRarity;
     }
 
+    @Unique
     @Override
     public boolean blade_addons$hasItemRarity() {
         return itemRarity != ItemRarity.NONE;
     }
 
+    @Unique
     @Override
     public void blade_addons$setItemRarity(ItemRarity itemRarity) {
         this.itemRarity = itemRarity;
     }
 
+    @Unique
     @Override
     public boolean blade_addons$hasScanned() {
         return itemRarity != null;
     }
 
+    @Unique
     @Override
     public boolean blade_addons$hasScannedPet() {
         return scannedPet;
     }
 
+    @Unique
     @Override
     public void blade_addons$setSelected(boolean selected) {
         selectedPet = selected;
         scannedPet = true;
     }
 
+    @Unique
     @Override
     public boolean blade_addons$isSelected() {
         return selectedPet;
     }
 
+    @Unique
     @Override
     public boolean blade_addons$hasScannedStars() {
         return scannedStars;
     }
 
+    @Unique
     @Override
     public void blade_addons$setStarCount(int count) {
         starCount = count;
         scannedStars = true;
     }
 
-
+    @Unique
     @Override
     public int blade_addons$getStarCount() {
         return starCount;
     }
 
+    @Unique
     @Override
     public boolean blade_addons$hasScannedProtection() {
         return scannedProtected;
     }
 
+    @Unique
     @Override
     public void blade_addons$setProtected(boolean value) {
         scannedProtected = true;
         isItemProtected = value;
     }
 
+    @Unique
     @Override
     public boolean blade_addons$isProtected() {
         return isItemProtected;
     }
 
+    @Unique
     @Override
     public boolean blade_addons$scannedMask() {
         return maskType != null;
     }
 
+    @Unique
     @Override
     public void blade_addons$setMask(MaskType maskType) {
         this.maskType = maskType;
     }
 
+    @Unique
     @Override
     public MaskType blade_addons$getMask() {
         return maskType;
diff --git a/src/main/java/blade/addon/utils/Misc.java b/src/main/java/blade/addon/utils/Misc.java
index ad9eb4f..6a27658 100644
--- a/src/main/java/blade/addon/utils/Misc.java
+++ b/src/main/java/blade/addon/utils/Misc.java
@@ -67,7 +67,7 @@ public class Misc {
     }
 
     public static void sendSound(SoundEvent soundEvent, float volume, float pitch) {
-       ClientPlayerEntity player = INSTANCE.player;
+        ClientPlayerEntity player = INSTANCE.player;
         if (player == null) return;
         forceMainThread(() -> player.playSound(soundEvent, volume, pitch));
     }
@@ -75,7 +75,8 @@ public class Misc {
     public static void sendSound(SoundData soundData) {
         sendSound(SoundEvent.of(soundData.getSound()), soundData.getVolume(), soundData.getPitch());
     }
-        public static void forceMainThread(Runnable runnable) {
+
+    public static void forceMainThread(Runnable runnable) {
         if (INSTANCE.isOnThread()) {
             runnable.run();
         } else {
diff --git a/src/main/java/blade/addon/utils/config/Config.java b/src/main/java/blade/addon/utils/config/Config.java
index eb15d14..fdb2f57 100644
--- a/src/main/java/blade/addon/utils/config/Config.java
+++ b/src/main/java/blade/addon/utils/config/Config.java
@@ -75,8 +75,8 @@ public class Config {
         invincibility.add(new ConfigOptions<>(Text.literal("Display when"), InvincibilityTimer.DisplayWhen.values(), () -> Dungeons.displayWhen, when -> Dungeons.displayWhen = when));
         invincibility.add(new ConfigBool(Text.literal("Use sprites"), () -> Dungeons.useSprites, bool -> Dungeons.useSprites = bool));
         invincibility.add(new ConfigBool(Text.literal("Show title on proc"), () -> Dungeons.showProcTitle, bool -> Dungeons.showProcTitle = bool));
-        invincibility.add(new ConfigBool(Text.literal("invincibility duration"), () -> Dungeons.InvincibilityDuration, bool -> Dungeons.InvincibilityDuration = bool));
-
+        invincibility.add(new ConfigBool(Text.literal("Invincibility duration"), () -> Dungeons.InvincibilityDuration, bool -> Dungeons.InvincibilityDuration = bool));
+        invincibility.add(new ConfigBool(Text.literal("Duration changing color"), () -> Dungeons.useStatusColorForInvincibility, bool -> Dungeons.useStatusColorForInvincibility = bool));
         dungeons.add(invincibility);
 
         ConfigSection classColors = new ConfigSection(Text.literal("Class Colors"));
@@ -106,7 +106,6 @@ public class Config {
         chests.add(new ConfigBool(Text.literal("Only display after run is over"), () -> Dungeons.onlyAfterRunOver, bool -> Dungeons.onlyAfterRunOver = bool));
         chests.add(new ConfigBool(Text.literal("Send chest count warning"), () -> Dungeons.sendChestWarning, bool -> Dungeons.sendChestWarning = bool));
         chests.add(new ConfigInt(Text.literal("Warning at chest"), () -> Dungeons.chestWarningCount, num -> Dungeons.chestWarningCount = num, 1, 1, 60));
-        chests.add(new ConfigColor(Text.literal("Widget color"), () -> Dungeons.chestCountColor, color -> Dungeons.chestCountColor = color, "chest-color", false));
 
         dungeons.add(chests);
         dungeons.add(new ConfigBool(Text.literal("Leap message"), () -> Dungeons.enableLeapMessages, bool -> Dungeons.enableLeapMessages = bool));
@@ -118,6 +117,7 @@ public class Config {
         dungeons.add(expo);
 
         dungeons.add(new ConfigBool(Text.literal("Notification on key spawn"), () -> Dungeons.enableKeyNotifier, bool -> Dungeons.enableKeyNotifier = bool));
+        dungeons.add(new ConfigBool(Text.literal("Display key noti for all classes"), () -> Dungeons.displayKeyForAllClasses, bool -> Dungeons.displayKeyForAllClasses = bool));
         dungeons.add(new ConfigBool(Text.literal("Secret spawn timer"), () -> Dungeons.enableSecretSpawnTimer, bool -> Dungeons.enableSecretSpawnTimer = bool));
         dungeons.add(new ConfigBool(Text.literal("Item highlight"), () -> Dungeons.highlightItems, bool -> Dungeons.highlightItems = bool));
         dungeons.add(new ConfigBool(Text.literal("Combine screen notifications"), () -> Dungeons.combineScreenNotifications, bool -> Dungeons.combineScreenNotifications = bool));
@@ -127,6 +127,7 @@ public class Config {
         dungeons.add(new ConfigBool(Text.literal("Mask cooldown highlight"), () -> Dungeons.maskHighlight, bool -> Dungeons.maskHighlight = bool));
         dungeons.add(new ConfigBool(Text.literal("Highlight teammates"), () -> Dungeons.highlightTeammates, bool -> Dungeons.highlightTeammates = bool));
         dungeons.add(new ConfigBool(Text.literal("Render class names"), () -> Dungeons.renderClassName, bool -> Dungeons.renderClassName = bool));
+        dungeons.add(new ConfigBool(Text.literal("Time until quiz question"), () -> Dungeons.quizTimer, bool -> Dungeons.quizTimer = bool));
         return dungeons;
     }
 
@@ -138,6 +139,7 @@ public class Config {
         ConfigSection waypoints = new ConfigSection(Text.literal("Waypoints"));
         waypoints.add(new ConfigBool(Text.literal("Enable boss waypoints"), () -> Floor7.enableBossWaypoints, bool -> Floor7.enableBossWaypoints = bool));
         waypoints.add(new ConfigBool(Text.literal("Enable placing waypoints"), BossWaypoints::getPlace, BossWaypoints::setPlace));
+        waypoints.add(new ConfigBool(Text.literal("temporarily ignore boss"), BossWaypoints::getIgnoreBoss, BossWaypoints::setIgnoreBoss));
         waypoints.add(new ConfigColor(Text.literal("Next waypoint color"), () -> Floor7.nextWaypointColor, color -> Floor7.nextWaypointColor = color, "next-waypoint-color", true));
         waypoints.add(new ConfigBool(Text.literal("Next waypoint no depth check"), () -> Floor7.nextWaypointThroughWall, bool -> Floor7.nextWaypointThroughWall = bool));
         floor7.add(waypoints);
@@ -145,6 +147,7 @@ public class Config {
         ConfigSection maxor = new ConfigSection(Text.literal("Maxor"));
         maxor.add(new ConfigBool(Text.literal("Crystal Spawn Time"), () -> Floor7.enableCrystalSpawnTime, bool -> Floor7.enableCrystalSpawnTime = bool));
         maxor.add(new ConfigBool(Text.literal("Crystal place reminder"), () -> Floor7.crystalPlaceReminder, bool -> Floor7.crystalPlaceReminder = bool));
+        maxor.add(new ConfigBool(Text.literal("Display reminder instantly"), () -> Floor7.instantlyDisplayCrystalReminder, bool -> Floor7.instantlyDisplayCrystalReminder = bool));
         floor7.add(maxor);
 
         ConfigSection storm = new ConfigSection(Text.literal("Storm"));
@@ -275,11 +278,12 @@ public class Config {
         extra.add(new ConfigBool(Text.literal("Display kicked time"), () -> ExtraOptions.enableKickedTimer, bool -> ExtraOptions.enableKickedTimer = bool));
         extra.add(new ConfigBool(Text.literal("Disable recipe book"), () -> ExtraOptions.disableRecipeBook, bool -> ExtraOptions.disableRecipeBook = bool));
         extra.add(new ConfigBool(Text.literal("Enable moveable held item tooltip"), () -> ExtraOptions.moveToolTip, bool -> ExtraOptions.moveToolTip = bool));
-        extra.add(new ConfigColor(Text.literal("Timer text color prefix"), () -> ExtraOptions.timerPrefixColor, color -> ExtraOptions.timerPrefixColor = color, "timer-prefix", false));
+        extra.add(new ConfigColor(Text.literal("Prefix text color"), () -> ExtraOptions.timerPrefixColor, color -> ExtraOptions.timerPrefixColor = color, "timer-prefix", false));
 
         ConfigSection sound = new ConfigSection(Text.literal("Sound options"));
         sound.add(new ConfigBool(Text.literal("Disable \"on cooldown\" sound"), () -> ExtraOptions.disableAbilityCooldownSound, bool -> ExtraOptions.disableAbilityCooldownSound = bool));
         sound.add(new ConfigBool(Text.literal("Disable bonzo sound"), () -> ExtraOptions.disableBonzoSound, bool -> ExtraOptions.disableBonzoSound = bool));
+        sound.add(new ConfigBool(Text.literal("Old bonzo sound"), () -> ExtraOptions.oldBonzoSound, bool -> ExtraOptions.oldBonzoSound = bool));
         extra.add(sound);
 
         ConfigSection rag = new ConfigSection(Text.literal("Ragnarock"));
@@ -302,7 +306,7 @@ public class Config {
         pets.add(new ConfigBool(Text.literal("Draw the pets sprite"), () -> ExtraOptions.includePetSprite, bool -> ExtraOptions.includePetSprite = bool));
         pets.add(new ConfigBool(Text.literal("Send sound on petswap"), () -> ExtraOptions.sendOnPetSound, bool -> ExtraOptions.sendOnPetSound = bool));
         pets.add(new ConfigSound(Text.literal("Pet swap sound"), ExtraOptions.petSound));
-        pets.add(new ConfigBool(Text.literal("Swap pet notification"), () -> ExtraOptions.sendPetSwapNotification, bool -> ExtraOptions.sendPetSwapNotification = bool));
+        pets.add(new ConfigBool(Text.literal("Pet swap notification"), () -> ExtraOptions.sendPetSwapNotification, bool -> ExtraOptions.sendPetSwapNotification = bool));
         extra.add(pets);
 
         ConfigSection diana = new ConfigSection(Text.literal("Diana notifications"));
diff --git a/src/main/java/blade/addon/utils/config/components/Components.java b/src/main/java/blade/addon/utils/config/components/Components.java
index 6bacddd..523591e 100644
--- a/src/main/java/blade/addon/utils/config/components/Components.java
+++ b/src/main/java/blade/addon/utils/config/components/Components.java
@@ -1,6 +1,7 @@
 package blade.addon.utils.config.components;
 
 import blade.addon.features.dungeon.ChestCounter;
+import blade.addon.features.dungeon.QuizTimer;
 import blade.addon.features.dungeon.RunStartValidator;
 import blade.addon.features.dungeon.KeyNotifier;
 import blade.addon.features.dungeon.SecretSpawnTimer;
@@ -134,4 +135,7 @@ public class Components {
 
     @ConfigValue
     public static HUDComponent sectionProgressDisplay = new HUDComponent(0, 0, 30, 10, 1, "Section progress", SectionProgress::display,  SectionProgress::render, () -> Floor7.showSectionProgress);
+
+    @ConfigValue
+    public static HUDComponent quizTimerDisplay = new HUDComponent(0, 0, 60, 10, 1, "Quiz timer", QuizTimer::display,  QuizTimer::render, () -> Dungeons.quizTimer);
 }
diff --git a/src/main/java/blade/addon/utils/config/values/Dungeons.java b/src/main/java/blade/addon/utils/config/values/Dungeons.java
index ca539f4..d1fcb4e 100644
--- a/src/main/java/blade/addon/utils/config/values/Dungeons.java
+++ b/src/main/java/blade/addon/utils/config/values/Dungeons.java
@@ -47,9 +47,6 @@ public class Dungeons {
     @ConfigValue
     public static int chestWarningCount = 55;
 
-    @ConfigValue
-    public static int chestCountColor = 0xffffffff;
-
     @ConfigValue
     public static boolean enableLeapMessages = false;
 
@@ -119,6 +116,9 @@ public class Dungeons {
     @ConfigValue
     public static boolean InvincibilityDuration = false;
 
+    @ConfigValue
+    public static boolean useStatusColorForInvincibility = false;
+
     @ConfigValue
     public static boolean highlightTeammates = false;
 
@@ -127,4 +127,10 @@ public class Dungeons {
 
     @ConfigValue
     public static boolean dontHighlightHiddenTeammates = false;
+
+    @ConfigValue
+    public static boolean displayKeyForAllClasses = false;
+
+    @ConfigValue
+    public static boolean quizTimer = false;
 }
diff --git a/src/main/java/blade/addon/utils/config/values/ExtraOptions.java b/src/main/java/blade/addon/utils/config/values/ExtraOptions.java
index de0eafc..f1a5d71 100644
--- a/src/main/java/blade/addon/utils/config/values/ExtraOptions.java
+++ b/src/main/java/blade/addon/utils/config/values/ExtraOptions.java
@@ -102,4 +102,7 @@ public class ExtraOptions {
 
     @ConfigValue
     public static int timerPrefixColor = Constants.DARK_PURPLE;
+
+    @ConfigValue
+    public static boolean oldBonzoSound = false;
 }
diff --git a/src/main/java/blade/addon/utils/config/values/Floor7.java b/src/main/java/blade/addon/utils/config/values/Floor7.java
index bdb3770..5df8051 100644
--- a/src/main/java/blade/addon/utils/config/values/Floor7.java
+++ b/src/main/java/blade/addon/utils/config/values/Floor7.java
@@ -142,4 +142,7 @@ public class Floor7 {
     @ConfigValue
     public static boolean disableTitlesAtSS = false;
 
+    @ConfigValue
+    public static boolean instantlyDisplayCrystalReminder = false;
+
 }
diff --git a/src/main/java/blade/addon/utils/interfaces/ArmourStandHolder.java b/src/main/java/blade/addon/utils/interfaces/ArmourStandHolder.java
deleted file mode 100644
index e389100..0000000
--- a/src/main/java/blade/addon/utils/interfaces/ArmourStandHolder.java
+++ /dev/null
@@ -1,10 +0,0 @@
-package blade.addon.utils.interfaces;
-
-public interface ArmourStandHolder {
-
-    boolean scanned = false;
-
-    boolean blade_addons$hasBeenScanned();
-
-    void blade_addons$setScanned(boolean value);
-}
diff --git a/src/main/java/blade/addon/utils/rendering/RenderLayers.java b/src/main/java/blade/addon/utils/rendering/RenderLayers.java
index 9e195bb..a4ce157 100644
--- a/src/main/java/blade/addon/utils/rendering/RenderLayers.java
+++ b/src/main/java/blade/addon/utils/rendering/RenderLayers.java
@@ -26,7 +26,6 @@ public class RenderLayers {
                     .layering(RenderPhase.VIEW_OFFSET_Z_LAYERING)
                     .build(false));
 
-
     public static RenderLayer getOutline(int width, boolean depthCheck) {
         if (depthCheck) {
             if (OUTLINE_WIDTHS.containsKey(width)) {
diff --git a/src/main/java/blade/addon/utils/rendering/RenderPipelines.java b/src/main/java/blade/addon/utils/rendering/RenderPipelines.java
index b861843..9afd2f4 100644
--- a/src/main/java/blade/addon/utils/rendering/RenderPipelines.java
+++ b/src/main/java/blade/addon/utils/rendering/RenderPipelines.java
@@ -31,7 +31,6 @@ public class RenderPipelines {
             .withDepthWrite(true)
             .build());
 
-
     public static final RenderPipeline OUTLINE_ENTITY_PIPELINE_NO_DEPTH = net.minecraft.client.gl.RenderPipelines.register(RenderPipeline.builder(net.minecraft.client.gl.RenderPipelines.RENDERTYPE_LINES_SNIPPET)
             .withLocation(Identifier.of(Constants.NAMESPACE, "outline-entity-no-depth"))
             .withDepthTestFunction(DepthTestFunction.NO_DEPTH_TEST)
diff --git a/src/main/java/blade/addon/utils/rendering/RenderUtils.java b/src/main/java/blade/addon/utils/rendering/RenderUtils.java
index 596bdef..f77e3b7 100644
--- a/src/main/java/blade/addon/utils/rendering/RenderUtils.java
+++ b/src/main/java/blade/addon/utils/rendering/RenderUtils.java
@@ -26,6 +26,10 @@ public class RenderUtils {
         return new float[]{r, g, b, a};
     }
 
+    public static int getStatusColor(int lastGreenTick, int lastOrangeTick, int currentTick) {
+        return currentTick >= lastGreenTick ? Constants.GREEN_COLOR : currentTick >= lastOrangeTick ? Constants.ORANGE_COLOR : Constants.RED_COLOR;
+    }
+
     public static void drawCenteredText(DrawContext context, TextRenderer textRenderer, Text text, int x, int y, int maxWidth, int color) {
         if (textRenderer == null) return;
         int centered = (maxWidth - textRenderer.getWidth(text)) / 2;
@@ -57,16 +61,29 @@ public class RenderUtils {
         RenderUtils.drawCenteredText(context, MinecraftClient.getInstance().textRenderer, Text.literal(Constants.DECIMAL_FORMAT.format(num)), x, y, component.getWidth(), color);
     }
 
-    public static void drawPrefixedTimer(HUDComponent component, DrawContext context, double num, String text) {
-        Text drawnText = Text.literal(text + ": ").withColor(ExtraOptions.timerPrefixColor).append(Text.literal(Constants.DECIMAL_FORMAT.format(num) + "s").withColor(0xffffffff));
-        RenderUtils.drawCenteredText(context, component, drawnText);
+    public static void drawPrefixedTimer(HUDComponent component, DrawContext context,  String prefix, int num) {
+        drawPrefixedText(component, context, prefix, Constants.DECIMAL_FORMAT.format(num * Constants.TICK_DURATION) + "s");
+    }
+
+    public static void drawPrefixedTimer(HUDComponent component, DrawContext context, String prefix, double num) {
+        drawPrefixedText(component, context, prefix, Constants.DECIMAL_FORMAT.format(num) + "s");
+    }
+
+    public static void drawPrefixedText(HUDComponent component, DrawContext context, String prefix, String text) {
+        TextRenderer textRenderer = MinecraftClient.getInstance().textRenderer;
+        if (textRenderer == null) return;
+
+        Text drawnText = Text.literal(prefix + ": ").withColor(ExtraOptions.timerPrefixColor).append(Text.literal(text).withColor(0xffffffff));
+        context.drawText(textRenderer, drawnText, component.getScaledX(), component.getScaledY(), 0xffffffff, true);
     }
 
     public static void renderFilled(MatrixStack matrixStack, VertexConsumer consumer, Box box, float[] rgba) {
+        if (rgba[3] == 0) return;
         VertexRendering.drawFilledBox(matrixStack, consumer, box.minX, box.minY, box.minZ, box.maxX, box.maxY, box.maxZ, rgba[0], rgba[1], rgba[2], rgba[3]);
     }
 
     public static void renderOutline(MatrixStack matrixStack, VertexConsumer consumer, Box box, float[] rgba) {
+        if (rgba[3] == 0) return;
         VertexRendering.drawBox(matrixStack.peek(), consumer, box, rgba[0], rgba[1], rgba[2], rgba[3]);
     }
 
diff --git a/src/main/java/blade/addon/utils/rendering/RenderingEvents.java b/src/main/java/blade/addon/utils/rendering/RenderingEvents.java
index 39f0ba4..b5147d6 100644
--- a/src/main/java/blade/addon/utils/rendering/RenderingEvents.java
+++ b/src/main/java/blade/addon/utils/rendering/RenderingEvents.java
@@ -17,8 +17,8 @@ public class RenderingEvents {
     public static RenderHandler NO_DEPTH_OUTLINE_ENTITY = new RenderHandler();
 
     public static void init() {
-        WorldRenderEvents.BEFORE_ENTITIES.register(RenderingEvents::filled);
-        WorldRenderEvents.BEFORE_ENTITIES.register(RenderingEvents::filledNoDepth);
+        WorldRenderEvents.BEFORE_DEBUG_RENDER.register(RenderingEvents::filled);
+        WorldRenderEvents.BEFORE_DEBUG_RENDER.register(RenderingEvents::filledNoDepth);
         WorldRenderEvents.AFTER_ENTITIES.register(RenderingEvents::entityFilled);
         WorldRenderEvents.AFTER_ENTITIES.register(RenderingEvents::entityOutline);
         WorldRenderEvents.AFTER_ENTITIES.register(RenderingEvents::entityOutlineNoDepth);
diff --git a/src/main/resources/assets/blade-addons/sounds.json b/src/main/resources/assets/blade-addons/sounds.json
index ab55799..d8ba31c 100644
--- a/src/main/resources/assets/blade-addons/sounds.json
+++ b/src/main/resources/assets/blade-addons/sounds.json
@@ -3,5 +3,16 @@
     "sounds": [
       "blade-addons:wolf-howl"
     ]
+  },
+  "ghast-moan": {
+    "sounds": [
+      { "name": "blade-addons:ghast-moan-1", "weight": 1 },
+      { "name": "blade-addons:ghast-moan-2", "weight": 1 },
+      { "name": "blade-addons:ghast-moan-3", "weight": 1 },
+      { "name": "blade-addons:ghast-moan-4", "weight": 1 },
+      { "name": "blade-addons:ghast-moan-5", "weight": 1 },
+      { "name": "blade-addons:ghast-moan-6", "weight": 1 },
+      { "name": "blade-addons:ghast-moan-7", "weight": 1 }
+    ]
   }
 }
\ No newline at end of file
diff --git a/src/main/resources/assets/blade-addons/sounds/ghast-moan-1.ogg b/src/main/resources/assets/blade-addons/sounds/ghast-moan-1.ogg
new file mode 100644
index 0000000..2faa7d5
Binary files /dev/null and b/src/main/resources/assets/blade-addons/sounds/ghast-moan-1.ogg differ
diff --git a/src/main/resources/assets/blade-addons/sounds/ghast-moan-2.ogg b/src/main/resources/assets/blade-addons/sounds/ghast-moan-2.ogg
new file mode 100644
index 0000000..c881055
Binary files /dev/null and b/src/main/resources/assets/blade-addons/sounds/ghast-moan-2.ogg differ
diff --git a/src/main/resources/assets/blade-addons/sounds/ghast-moan-3.ogg b/src/main/resources/assets/blade-addons/sounds/ghast-moan-3.ogg
new file mode 100644
index 0000000..54dc592
Binary files /dev/null and b/src/main/resources/assets/blade-addons/sounds/ghast-moan-3.ogg differ
diff --git a/src/main/resources/assets/blade-addons/sounds/ghast-moan-4.ogg b/src/main/resources/assets/blade-addons/sounds/ghast-moan-4.ogg
new file mode 100644
index 0000000..da3a6cf
Binary files /dev/null and b/src/main/resources/assets/blade-addons/sounds/ghast-moan-4.ogg differ
diff --git a/src/main/resources/assets/blade-addons/sounds/ghast-moan-5.ogg b/src/main/resources/assets/blade-addons/sounds/ghast-moan-5.ogg
new file mode 100644
index 0000000..a1dba01
Binary files /dev/null and b/src/main/resources/assets/blade-addons/sounds/ghast-moan-5.ogg differ
diff --git a/src/main/resources/assets/blade-addons/sounds/ghast-moan-6.ogg b/src/main/resources/assets/blade-addons/sounds/ghast-moan-6.ogg
new file mode 100644
index 0000000..43522e8
Binary files /dev/null and b/src/main/resources/assets/blade-addons/sounds/ghast-moan-6.ogg differ
diff --git a/src/main/resources/assets/blade-addons/sounds/ghast-moan-7.ogg b/src/main/resources/assets/blade-addons/sounds/ghast-moan-7.ogg
new file mode 100644
index 0000000..867ba35
Binary files /dev/null and b/src/main/resources/assets/blade-addons/sounds/ghast-moan-7.ogg differ
diff --git a/src/main/resources/assets/blade-addons/sounds/ghast-moan.ogg b/src/main/resources/assets/blade-addons/sounds/ghast-moan.ogg
new file mode 100644
index 0000000..2faa7d5
Binary files /dev/null and b/src/main/resources/assets/blade-addons/sounds/ghast-moan.ogg differ
diff --git a/src/main/resources/blade-addons.mixins.json b/src/main/resources/blade-addons.mixins.json
index 1f3b621..d92b835 100644
--- a/src/main/resources/blade-addons.mixins.json
+++ b/src/main/resources/blade-addons.mixins.json
@@ -3,7 +3,6 @@
   "package": "blade.addon.mixin",
   "compatibilityLevel": "JAVA_21",
   "mixins": [
-    "ArmorStandEntityMixin",
     "ChunkMixin",
     "ClientConnectionMixin",
     "ClientPlayNetworkHandlerMixin",
- refactored alot - atp i dont even rememberGabriel Ellnebrand · 2026-01-24 · c015c4b
查看变更
gradle.properties                                                                              |   2 +-
 src/main/java/blade/addon/Bladeaddons.java                                                     |  11 ++++++--
 src/main/java/blade/addon/features/dungeon/HidePlayers.java                                    |   7 ++---
 src/main/java/blade/addon/features/dungeon/KeyNotifier.java                                    |  67 ++++++++++++++++++++++++++++++++--------------
 src/main/java/blade/addon/features/dungeon/RunStartValidator.java                              |  11 ++++----
 src/main/java/blade/addon/features/dungeon/WarpCooldown.java                                   |   9 ++-----
 src/main/java/blade/addon/features/dungeon/f7/CrystalSpawn.java                                |  36 ++++++++++++++++++-------
 src/main/java/blade/addon/features/dungeon/f7/PillarExplode.java                               |   7 +----
 src/main/java/blade/addon/features/dungeon/f7/RelicTimer.java                                  |  28 +++++++++----------
 src/main/java/blade/addon/features/dungeon/f7/invincibility/InvincibilityTimer.java            |   8 +++---
 src/main/java/blade/addon/features/dungeon/f7/location/LocationNotifier.java                   |   6 +----
 src/main/java/blade/addon/features/dungeon/f7/location/PositionMessage.java                    |   3 ++-
 src/main/java/blade/addon/features/dungeon/f7/terms/{Pre4Notifier.java => DeviceNotifier.java} |  31 ++++++++++++++-------
 src/main/java/blade/addon/features/dungeon/f7/terms/LeapNotification.java                      |   7 +----
 src/main/java/blade/addon/features/dungeon/f7/terms/MelodyWarning.java                         |   7 +----
 src/main/java/blade/addon/features/dungeon/f7/terms/SectionProgress.java                       |  51 +++++++++++++++++++++++++++++++++++
 src/main/java/blade/addon/features/dungeon/f7/terms/TermStartTimer.java                        |   7 +----
 src/main/java/blade/addon/features/dungeon/f7/terms/TitleHider.java                            |   2 +-
 src/main/java/blade/addon/features/highlight/MobHighlight.java                                 |   5 ++--
 src/main/java/blade/addon/features/highlight/TeammateHighlight.java                            |  72 +++++++++++++++++++++++++++++++++++++++++++++++++
 src/main/java/blade/addon/features/highlight/WitherHighlight.java                              |   4 +--
 src/main/java/blade/addon/features/item/HeldItemToolTip.java                                   |   3 ++-
 src/main/java/blade/addon/features/other/KickedTimer.java                                      |  10 ++-----
 src/main/java/blade/addon/features/other/SelectedPet.java                                      |  24 ++++++++++++++---
 src/main/java/blade/addon/mixin/ClientConnectionMixin.java                                     |   9 +++----
 src/main/java/blade/addon/mixin/ClientPlayNetworkHandlerMixin.java                             |  31 +--------------------
 src/main/java/blade/addon/mixin/ClientPlayerEntityMixin.java                                   |  18 ++++++-------
 src/main/java/blade/addon/mixin/DrawContextMixin.java                                          |  35 +++++++++++++++++++++++-
 src/main/java/blade/addon/mixin/EntityRendererMixin.java                                       |  33 +++++++++++------------
 src/main/java/blade/addon/mixin/InGameHudMixin.java                                            |   6 ++---
 src/main/java/blade/addon/mixin/MinecraftClientMixin.java                                      |   6 ++---
 src/main/java/blade/addon/mixin/PlayerEntityMixin.java                                         |  23 ----------------
 src/main/java/blade/addon/mixin/SkullBlockEntityRendererMixin.java                             |  44 ++++++++++++++++++++++++++++++
 src/main/java/blade/addon/utils/Keybinds.java                                                  |  83 ++++++++++++++++++++++++++++++++++++++++++++++-----------
 src/main/java/blade/addon/utils/Location.java                                                  |   8 +++---
 src/main/java/blade/addon/utils/config/Config.java                                             |  23 +++++++++++++---
 src/main/java/blade/addon/utils/config/components/CombinedScreenNotifications.java             |  30 ++++++++++++---------
 src/main/java/blade/addon/utils/config/components/CombinedTickTimer.java                       |   9 +++----
 src/main/java/blade/addon/utils/config/components/Components.java                              |  14 ++++++++--
 src/main/java/blade/addon/utils/config/values/Dungeons.java                                    |   9 +++++++
 src/main/java/blade/addon/utils/config/values/ExtraOptions.java                                |   7 +++++
 src/main/java/blade/addon/utils/config/values/Floor7.java                                      |  13 +++++++++
 src/main/java/blade/addon/utils/config/values/Visual.java                                      |   6 +++++
 src/main/java/blade/addon/utils/data/EntityUtil.java                                           |  56 +++++++++++++++++++++++++++++++++-----
 src/main/java/blade/addon/utils/debug/Debug.java                                               |   4 +--
 src/main/java/blade/addon/utils/dungeon/DungeonClass.java                                      |  80 +++++++++++++++++++++++++++---------------------------
 src/main/java/blade/addon/utils/events/Events.java                                             |   4 +++
 src/main/java/blade/addon/utils/events/interfaces/WorldEvent.java                              |   7 +++++
 src/main/java/blade/addon/utils/interfaces/PlayerDataHolder.java                               |  11 --------
 src/main/java/blade/addon/utils/rendering/RenderLayers.java                                    |  54 ++++++++++++++++++++-----------------
 src/main/java/blade/addon/utils/rendering/RenderPipelines.java                                 |  24 ++++++++---------
 src/main/java/blade/addon/utils/rendering/RenderUtils.java                                     |  42 ++++++++++++++++++++---------
 src/main/java/blade/addon/utils/rendering/RenderingEvents.java                                 |  23 +++++++++++++---
 src/main/resources/assets/blade-addons/textures/entity/essence.png                             | Bin 0 -> 1018 bytes
 src/main/resources/blade-addons.mixins.json                                                    |   2 +-
 55 files changed, 755 insertions(+), 377 deletions(-)
diff --git a/gradle.properties b/gradle.properties
index 127722c..e3549cb 100644
--- a/gradle.properties
+++ b/gradle.properties
@@ -20,6 +20,6 @@ archives_base_name=blade-addons
 # Dependencies
 fabric_version=0.138.4+1.21.10
 # I love dependencies sometimes breaking
-practical_config=1.4.1
+practical_config=1.4.3
 modmenu_version = 16.0.0-rc.2
 hypixel_api_version = 1.0.1
\ No newline at end of file
diff --git a/src/main/java/blade/addon/Bladeaddons.java b/src/main/java/blade/addon/Bladeaddons.java
index ae48c80..10399dc 100644
--- a/src/main/java/blade/addon/Bladeaddons.java
+++ b/src/main/java/blade/addon/Bladeaddons.java
@@ -25,12 +25,14 @@ import blade.addon.features.dungeon.f7.location.PositionMessages;
 import blade.addon.features.dungeon.f7.terms.GoldorTickTimer;
 import blade.addon.features.dungeon.f7.terms.LeapNotification;
 import blade.addon.features.dungeon.f7.terms.MelodyWarning;
-import blade.addon.features.dungeon.f7.terms.Pre4Notifier;
+import blade.addon.features.dungeon.f7.terms.DeviceNotifier;
+import blade.addon.features.dungeon.f7.terms.SectionProgress;
 import blade.addon.features.dungeon.f7.terms.TermStartTimer;
 import blade.addon.features.highlight.BatHighlight;
 import blade.addon.features.highlight.MimicHighlight;
 import blade.addon.features.highlight.MobHighlight;
 import blade.addon.features.highlight.SheepHighlight;
+import blade.addon.features.highlight.TeammateHighlight;
 import blade.addon.features.highlight.WitherHighlight;
 import blade.addon.features.item.DropAnimation;
 import blade.addon.features.notifications.Notifications;
@@ -48,6 +50,7 @@ import blade.addon.utils.config.Config;
 import blade.addon.utils.config.FolderUtility;
 import blade.addon.utils.config.components.Components;
 import blade.addon.utils.config.values.Buttons;
+import blade.addon.utils.data.EntityUtil;
 import blade.addon.utils.data.PartyUtil;
 import blade.addon.utils.debug.Debug;
 import blade.addon.utils.dungeon.DungeonClass;
@@ -72,7 +75,9 @@ public class Bladeaddons implements ModInitializer {
         Phase.init();
         Section.init();
         DungeonClass.init();
+
         PartyUtil.init();
+        EntityUtil.init();
 
         RenderingEvents.init();
 
@@ -103,7 +108,7 @@ public class Bladeaddons implements ModInitializer {
         LocationNotifier.init();
         PredevTimer.init();
         KickedTimer.init();
-        Pre4Notifier.init();
+        DeviceNotifier.init();
         PillarExplode.init();
         MelodyWarning.init();
         RagDisplay.init();
@@ -117,5 +122,7 @@ public class Bladeaddons implements ModInitializer {
         BatHighlight.init();
         WitherHighlight.init();
         InvincibilityDuration.init();
+        TeammateHighlight.init();
+        SectionProgress.init();
     }
 }
\ No newline at end of file
diff --git a/src/main/java/blade/addon/features/dungeon/HidePlayers.java b/src/main/java/blade/addon/features/dungeon/HidePlayers.java
index 97ee2c2..b7cf9b0 100644
--- a/src/main/java/blade/addon/features/dungeon/HidePlayers.java
+++ b/src/main/java/blade/addon/features/dungeon/HidePlayers.java
@@ -1,5 +1,6 @@
 package blade.addon.features.dungeon;
 
+import blade.addon.features.dungeon.f7.terms.DeviceNotifier;
 import blade.addon.utils.Location;
 import blade.addon.utils.config.values.Dungeons;
 import blade.addon.utils.data.EntityUtil;
@@ -8,14 +9,10 @@ import blade.addon.utils.events.Events;
 import net.minecraft.client.MinecraftClient;
 import net.minecraft.client.network.ClientPlayerEntity;
 import net.minecraft.entity.player.PlayerEntity;
-import net.minecraft.util.math.Vec3d;
 
 public class HidePlayers {
 
     private static final int HIDE_DURATION = 3 * 1000;
-    private static final double DISTANCE = 3;
-
-    private static final Vec3d SS_POSITION = new Vec3d(108, 119, 94);
 
     private static long startTime;
     private static boolean justLeapt = false;
@@ -45,7 +42,7 @@ public class HidePlayers {
         if (!Dungeons.hideAtSS || !Phase.inBoss()) return false;
         if (Dungeons.hideBeforeTermsOnly && Phase.inP3()) return false;
 
-        return SS_POSITION.distanceTo(player.getEntityPos()) <= DISTANCE;
+        return DeviceNotifier.atSS(player);
     }
 
     public static boolean testHideInRange(PlayerEntity player) {
diff --git a/src/main/java/blade/addon/features/dungeon/KeyNotifier.java b/src/main/java/blade/addon/features/dungeon/KeyNotifier.java
index ed5cddb..3c1894b 100644
--- a/src/main/java/blade/addon/features/dungeon/KeyNotifier.java
+++ b/src/main/java/blade/addon/features/dungeon/KeyNotifier.java
@@ -7,12 +7,19 @@ import blade.addon.utils.dungeon.DungeonClass;
 import blade.addon.utils.events.Events;
 import blade.addon.utils.rendering.RenderUtils;
 import config.practical.hud.HUDComponent;
-import net.fabricmc.fabric.api.client.message.v1.ClientReceiveMessageEvents;
+import net.fabricmc.fabric.api.client.event.lifecycle.v1.ClientTickEvents;
 import net.minecraft.client.MinecraftClient;
 import net.minecraft.client.gui.DrawContext;
+import net.minecraft.component.DataComponentTypes;
+import net.minecraft.component.type.ProfileComponent;
+import net.minecraft.entity.Entity;
+import net.minecraft.entity.EquipmentSlot;
+import net.minecraft.entity.decoration.ArmorStandEntity;
+import net.minecraft.item.ItemStack;
 import net.minecraft.sound.SoundEvents;
 import net.minecraft.text.Text;
 
+import java.util.concurrent.CopyOnWriteArrayList;
 import java.util.regex.Matcher;
 import java.util.regex.Pattern;
 
@@ -21,41 +28,58 @@ public class KeyNotifier {
     private final static Text BLOOD_KEY = Text.literal("§l§cBlood Key Dropped");
     private final static Text WITHER_KEY = Text.literal("§l§7Wither Key Dropped");
 
-    private final static Pattern KEY_ENTITY_PATTERN = Pattern.compile("(Wither|Blood) Key");
+    //Thanks to mio for this key detection system
+    private final static String WITHER_UUID = "2865274b-3097-394e-8149-ec629c72d850";
+    private final static String BLOOD_UUID = "73f6d1f9-df41-3d1d-b98c-e1442d915885";
+
     private final static Pattern AUTOMATIC_PICKUP_PATTERN = Pattern.compile("A (Wither|Blood) Key was picked up!");
     private final static Pattern NORMAL_PICKUP_PATTERN = Pattern.compile("has obtained (Wither|Blood) Key!");
 
+    private final static CopyOnWriteArrayList<Entity> foundKeys = new CopyOnWriteArrayList<>();
+
     private static boolean hasKey = false;
     private static boolean isBloodKey = false;
 
     public static void init() {
-        Events.ON_ENTITY_TRACKED.register((entity, world) -> {
-            if (!Location.inDungeon() || hasKey || !Dungeons.enableKeyNotifier) return false;
-            if (!DungeonClass.isClass(DungeonClass.ARCHER) && !DungeonClass.isClass(DungeonClass.MAGE)) return false;
-
-
-            Text text = entity.getCustomName();
-            if (text == null) return false;
+        ClientTickEvents.END_WORLD_TICK.register((world) -> {
+            if (!Location.inDungeon() || hasKey || !Dungeons.enableKeyNotifier) return;
+            if (!DungeonClass.isClass(DungeonClass.ARCHER) && !DungeonClass.isClass(DungeonClass.MAGE)) return;
 
-            Matcher matcher = KEY_ENTITY_PATTERN.matcher(text.getString());
-            if (matcher.find()) {
-                isBloodKey = matcher.group(1).matches("Blood");
-                hasKey = true;
-                Scheduler.scheduleSound(SoundEvents.ENTITY_EXPERIENCE_ORB_PICKUP, 2, 0.5f);
+            for (Entity entity : world.getEntities()) {
+                if (entity instanceof ArmorStandEntity armorStand && !foundKeys.contains(entity)) {
+                    ItemStack head = armorStand.getEquippedStack(EquipmentSlot.HEAD);
+                    ProfileComponent profile = head.get(DataComponentTypes.PROFILE);
+                    if (profile == null) continue;
+                    String uuid = profile.getGameProfile().id().toString();
+                    if (uuid == null) continue;
+
+                    if (uuid.equals(WITHER_UUID)) {
+                        foundKeys.add(entity);
+                        hasKey = true;
+                        Scheduler.scheduleSound(SoundEvents.ENTITY_EXPERIENCE_ORB_PICKUP, 2, 0.5f);
+                        return;
+                    }
+
+                    if (uuid.equals(BLOOD_UUID)) {
+                        foundKeys.add(entity);
+                        isBloodKey = true;
+                        hasKey = true;
+                        Scheduler.scheduleSound(SoundEvents.ENTITY_EXPERIENCE_ORB_PICKUP, 2, 0.5f);
+                        return;
+                    }
+                }
             }
-
-            return false;
         });
 
-        ClientReceiveMessageEvents.GAME.register((message, overlay) -> {
-            if (!Location.inDungeon() || !hasKey || !Dungeons.enableKeyNotifier) return;
-            if (!DungeonClass.isClass(DungeonClass.ARCHER) && !DungeonClass.isClass(DungeonClass.MAGE)) return;
+        Events.ON_GAME_MESSAGE.register(message -> {
+            if (!Location.inDungeon() || !hasKey || !Dungeons.enableKeyNotifier) return false;
+            if (!DungeonClass.isClass(DungeonClass.ARCHER) && !DungeonClass.isClass(DungeonClass.MAGE)) return false;
 
             Matcher matcher = AUTOMATIC_PICKUP_PATTERN.matcher(message.getString());
             if (matcher.find()) {
                 hasKey = false;
                 isBloodKey = false;
-                return;
+                return false;
             }
 
             matcher = NORMAL_PICKUP_PATTERN.matcher(message.getString());
@@ -63,12 +87,15 @@ public class KeyNotifier {
                 hasKey = false;
                 isBloodKey = false;
             }
+
+            return false;
         });
 
         Events.ON_LOCATION_CHANGE.register(locations -> {
             if (Location.inDungeon()) {
                 isBloodKey = false;
                 hasKey = false;
+                foundKeys.clear();
             }
             return false;
         });
diff --git a/src/main/java/blade/addon/features/dungeon/RunStartValidator.java b/src/main/java/blade/addon/features/dungeon/RunStartValidator.java
index 22323d2..102fc7c 100644
--- a/src/main/java/blade/addon/features/dungeon/RunStartValidator.java
+++ b/src/main/java/blade/addon/features/dungeon/RunStartValidator.java
@@ -61,7 +61,8 @@ public class RunStartValidator {
         Events.ON_PLAYER_ENTRY.register(receivedEntry -> {
             //will only check after the first game message in case of false positives
             if (!hasTicked) return false;
-            if (!Location.inDungeon() || Phase.runStarted() || (!Dungeons.detectDuplicateClass && !Dungeons.detectPlayerCount)) return false;
+            if (!Location.inDungeon() || Phase.runStarted() || (!Dungeons.detectDuplicateClass && !Dungeons.detectPlayerCount))
+                return false;
             validate();
             return false;
         });
@@ -135,14 +136,12 @@ public class RunStartValidator {
     }
 
     public static void render(HUDComponent component, DrawContext context) {
-        int x = component.getScaledX();
-        int y = component.getScaledY();
         if (hasDupeClasses) {
-            RenderUtils.drawCenteredText(context, MinecraftClient.getInstance().textRenderer, DUPE_CLASS_TEXT, x, y, component.getWidth());
+            RenderUtils.drawCenteredText(context, component, DUPE_CLASS_TEXT);
         } else if (notEnoughPlayers) {
-            RenderUtils.drawCenteredText(context, MinecraftClient.getInstance().textRenderer, PLAYER_COUNT_TEXT, x, y, component.getWidth());
+            RenderUtils.drawCenteredText(context, component, PLAYER_COUNT_TEXT);
         } else {
-            RenderUtils.drawCenteredText(context, MinecraftClient.getInstance().textRenderer, Text.literal("§cSome warning text"), x, y, component.getWidth());
+            RenderUtils.drawCenteredText(context, component, Text.literal("§cSome warning text"));
         }
     }
 }
diff --git a/src/main/java/blade/addon/features/dungeon/WarpCooldown.java b/src/main/java/blade/addon/features/dungeon/WarpCooldown.java
index cf46007..010f5db 100644
--- a/src/main/java/blade/addon/features/dungeon/WarpCooldown.java
+++ b/src/main/java/blade/addon/features/dungeon/WarpCooldown.java
@@ -1,12 +1,10 @@
 package blade.addon.features.dungeon;
 
-import blade.addon.utils.Constants;
 import blade.addon.utils.config.values.Dungeons;
+import blade.addon.utils.rendering.RenderUtils;
 import config.practical.hud.HUDComponent;
 import net.fabricmc.fabric.api.client.message.v1.ClientReceiveMessageEvents;
-import net.minecraft.client.MinecraftClient;
 import net.minecraft.client.gui.DrawContext;
-import net.minecraft.text.Text;
 
 import java.util.regex.Matcher;
 import java.util.regex.Pattern;
@@ -43,10 +41,7 @@ public class WarpCooldown {
     }
 
     public static void render(HUDComponent component, DrawContext context) {
-        int x = component.getScaledX();
-        int y = component.getScaledY();
-
         double timeRemaining = Math.max((endTime - System.currentTimeMillis()) / 1000.0, 0);
-        context.drawText(MinecraftClient.getInstance().textRenderer, Text.literal("§5Warp Cooldown: §f" + Constants.DECIMAL_FORMAT.format(timeRemaining)), x, y, 0xffffffff, true);
+        RenderUtils.drawPrefixedTimer(component, context, timeRemaining, "Warp Cooldown");
     }
 }
diff --git a/src/main/java/blade/addon/features/dungeon/f7/CrystalSpawn.java b/src/main/java/blade/addon/features/dungeon/f7/CrystalSpawn.java
index ab8c0c2..ff4cd97 100644
--- a/src/main/java/blade/addon/features/dungeon/f7/CrystalSpawn.java
+++ b/src/main/java/blade/addon/features/dungeon/f7/CrystalSpawn.java
@@ -21,34 +21,35 @@ import java.util.regex.Pattern;
 
 public class CrystalSpawn {
 
-    private static final Pattern PATTERN_1 = Pattern.compile("^\\[BOSS] Maxor: THAT BEAM! IT HURTS! IT HURTS!!$");
-    private static final Pattern PATTERN_2 = Pattern.compile("^\\[BOSS] Maxor: YOU TRICKED ME!$");
     private static final Pattern RELIC_PICK_UP = Pattern.compile("(\\w+) picked up an Energy Crystal!$");
 
+    private static final int REMINDER_TICK = 240;
+    private static final int TICK_SPAWN = 34;
+
     private static long pickupTime = 0;
     private static boolean pickedUp = false;
 
-    private static final int TICK_SPAWN = 34;
     private static int tick = 0;
+    private static int tickSincePicked = 0;
 
     public static void init() {
 
         Events.ON_GAME_MESSAGE.register(text -> {
-            if (!Location.inDungeon() || !Phase.inP1() || !Floor7.enableCrystalSpawnTime) return false;
+            if (!Location.inDungeon() || !Phase.inP1() || (!Floor7.enableCrystalSpawnTime && !Floor7.crystalPlaceReminder))
+                return false;
+            String string = text.getString();
 
-            Matcher matcher = PATTERN_1.matcher(text.getString());
-            if (matcher.find()) {
+            if (string.equals("[BOSS] Maxor: THAT BEAM! IT HURTS! IT HURTS!!")) {
                 tick = TICK_SPAWN;
                 return false;
             }
 
-            matcher = PATTERN_2.matcher(text.getString());
-            if (matcher.find()) {
+            if (string.equals("[BOSS] Maxor: YOU TRICKED ME!")) {
                 tick = TICK_SPAWN;
                 return false;
             }
 
-            matcher = RELIC_PICK_UP.matcher(text.getString());
+            Matcher matcher = RELIC_PICK_UP.matcher(text.getString());
             if (matcher.find()) {
                 String name = matcher.group(1);
                 if (EntityUtil.isClientPlayer(name)) {
@@ -61,11 +62,15 @@ public class CrystalSpawn {
         });
         Events.ON_SERVER_TICK.register(() -> {
             tick = Math.max(tick - 1, 0);
+            if (pickedUp) {
+                tickSincePicked++;
+            }
             return false;
         });
 
         Events.ON_LOCATION_CHANGE.register(newLocation -> {
             tick = 0;
+            tickSincePicked = 0;
             pickedUp = false;
             return false;
         });
@@ -82,6 +87,7 @@ public class CrystalSpawn {
                 if (distance < 6 && crystal.getY() == 224.375) {
                     PersonalBests.crystalTime.testNewTime(Text.literal("§aCrystal placed in "), pickupTime);
                     pickedUp = false;
+                    tickSincePicked = 0;
                 }
 
             }
@@ -90,10 +96,20 @@ public class CrystalSpawn {
     }
 
     public static boolean display() {
-        return tick > 0 && Location.inDungeon() && Floor7.enableCrystalSpawnTime;
+        return tick > 0 && Location.inDungeon() && Phase.inP1() && Floor7.enableCrystalSpawnTime;
     }
 
     public static void render(HUDComponent component, DrawContext context) {
         RenderUtils.drawTimer(component, context, tick, Constants.LIGHT_PURPLE);
     }
+
+    public static boolean displayNotification() {
+        return tickSincePicked > REMINDER_TICK && Location.inDungeon() && Phase.inP1() && Floor7.crystalPlaceReminder && pickedUp;
+    }
+
+    public static void renderNotification(HUDComponent component, DrawContext context) {
+        RenderUtils.drawCenteredText(context, component, Text.literal("§bPlace Crystal!"));
+    }
+
+
 }
diff --git a/src/main/java/blade/addon/features/dungeon/f7/PillarExplode.java b/src/main/java/blade/addon/features/dungeon/f7/PillarExplode.java
index 475081b..92e30c9 100644
--- a/src/main/java/blade/addon/features/dungeon/f7/PillarExplode.java
+++ b/src/main/java/blade/addon/features/dungeon/f7/PillarExplode.java
@@ -8,11 +8,9 @@ import blade.addon.utils.dungeon.Phase;
 import blade.addon.utils.events.Events;
 import blade.addon.utils.rendering.RenderUtils;
 import config.practical.hud.HUDComponent;
-import net.minecraft.client.MinecraftClient;
 import net.minecraft.client.gui.DrawContext;
 import net.minecraft.sound.SoundEvents;
 import net.minecraft.text.Text;
-import net.minecraft.util.Formatting;
 
 public class PillarExplode {
 
@@ -55,9 +53,6 @@ public class PillarExplode {
     }
 
     public static void render(HUDComponent component, DrawContext context) {
-        int x = component.getScaledX();
-        int y = component.getScaledY();
-        RenderUtils.drawCenteredText(context, MinecraftClient.getInstance().textRenderer, Text.literal("§6||| §bStorm crushed! §6|||"), x, y, component.getWidth());
-
+        RenderUtils.drawCenteredText(context, component, Text.literal("§6||| §bStorm crushed! §6|||"));
     }
 }
diff --git a/src/main/java/blade/addon/features/dungeon/f7/RelicTimer.java b/src/main/java/blade/addon/features/dungeon/f7/RelicTimer.java
index bdd5ec7..86cca09 100644
--- a/src/main/java/blade/addon/features/dungeon/f7/RelicTimer.java
+++ b/src/main/java/blade/addon/features/dungeon/f7/RelicTimer.java
@@ -17,7 +17,6 @@ import net.fabricmc.fabric.api.client.event.lifecycle.v1.ClientTickEvents;
 import net.fabricmc.fabric.api.client.rendering.v1.world.WorldRenderContext;
 import net.minecraft.client.MinecraftClient;
 import net.minecraft.client.gui.DrawContext;
-import net.minecraft.client.network.ClientPlayerEntity;
 import net.minecraft.client.render.VertexConsumer;
 import net.minecraft.client.render.VertexRendering;
 import net.minecraft.client.util.math.MatrixStack;
@@ -66,7 +65,6 @@ public class RelicTimer {
     private static int tick = Floor7.relicSpawnTicks;
     private static boolean sentRelicTimes = false;
 
-    private static boolean ignoreChecks = false;
     //only here to test the relic progress bar
     private static boolean forceGUI = false;
 
@@ -86,7 +84,7 @@ public class RelicTimer {
 
         Events.ON_LOCATION_CHANGE.register(newLocation -> {
             if (Location.inDungeon()) {
-               reset();
+                reset();
             }
             return false;
         });
@@ -107,9 +105,7 @@ public class RelicTimer {
                 String name = matcher.group(1);
                 String relicString = matcher.group(2);
 
-                ClientPlayerEntity player = MinecraftClient.getInstance().player;
-                if (player == null) return false;
-                if (name.equals(player.getName().getString())) {
+                if (EntityUtil.isClientPlayer(name)) {
                     pickedupRelic = Relic.valueOf(relicString.toUpperCase());
                     Debug.sendDebugMessage(Text.literal("Picked up relic " + relicString));
                 }
@@ -119,7 +115,7 @@ public class RelicTimer {
         });
 
         ClientTickEvents.END_WORLD_TICK.register(world -> {
-            if (!Location.inDungeon() || !Phase.inP5() || !Floor7.showAllRelicTimes ||sentRelicTimes) return;
+            if (!Location.inDungeon() || !Phase.inP5() || !Floor7.showAllRelicTimes || sentRelicTimes) return;
 
             Iterable<Entity> entities = world.getEntities();
 
@@ -133,7 +129,6 @@ public class RelicTimer {
                     for (Relic relic : Relic.values()) {
                         if (relic.placedTime != 0 && Misc.getDistance(relic.box.maxX, relic.box.maxZ, ax, az) < 1) {
                             relic.placedTime = System.currentTimeMillis();
-                            break;
                         }
                     }
                 }
@@ -150,15 +145,14 @@ public class RelicTimer {
 
 
         Events.ON_BLOCK_INTERACTION.register((result, itemStack) -> {
-            if (!ignoreChecks) {
-                if ((!Location.inDungeon() || !Phase.inP5())) return false;
-            }
+            if ((!Location.inDungeon() || !Phase.inP5())) return false;
 
             if (pickedupRelic == null) return false;
 
             BlockPos pos = result.getBlockPos();
 
-            if (!ItemUtil.itemHasName(itemStack, "Relic")) {
+            //skyblock menu check is there incase the item doesnt get updated
+            if (!ItemUtil.itemHasName(itemStack, "Relic") && !ItemUtil.itemHasName(itemStack, "Skyblock Menu")) {
                 if (Floor7.blockIncorrectRelicPlace) {
                     Debug.sendDebugMessage(Text.literal("Item: " + itemStack.getName()));
                     Misc.addChatMessage(Text.literal("Blocked a weird click"));
@@ -186,6 +180,7 @@ public class RelicTimer {
                 pickedupRelic = null;
             } else {
                 if (Floor7.blockIncorrectRelicPlace) {
+                    Debug.sendDebugMessage(Text.literal("Relic: " + pickedupRelic));
                     Misc.addChatMessage(Text.literal("incorrect click!"));
                     return true;
                 }
@@ -228,9 +223,12 @@ public class RelicTimer {
         }
     }
 
-    public static void testIgnoreChecks() {
-        ignoreChecks = !ignoreChecks;
-        Misc.addChatMessage(Text.literal("Ignore relic checks: ").append(Misc.getStatusText(ignoreChecks)));
+    public static void printRelic() {
+        Misc.addChatMessage(Text.literal("Relic: " + pickedupRelic));
+
+    }
+
+    public static void debugCommands() {
 
     }
 
diff --git a/src/main/java/blade/addon/features/dungeon/f7/invincibility/InvincibilityTimer.java b/src/main/java/blade/addon/features/dungeon/f7/invincibility/InvincibilityTimer.java
index 208cf43..e9e9de7 100644
--- a/src/main/java/blade/addon/features/dungeon/f7/invincibility/InvincibilityTimer.java
+++ b/src/main/java/blade/addon/features/dungeon/f7/invincibility/InvincibilityTimer.java
@@ -1,6 +1,6 @@
 package blade.addon.features.dungeon.f7.invincibility;
 
-import blade.addon.features.dungeon.f7.terms.Pre4Notifier;
+import blade.addon.features.dungeon.f7.terms.DeviceNotifier;
 import blade.addon.utils.Constants;
 import blade.addon.utils.Location;
 import blade.addon.utils.Misc;
@@ -111,21 +111,21 @@ public class InvincibilityTimer {
         Matcher matcher = BONZO_PATTERN.matcher(string);
         if (matcher.matches()) {
             bonzoMaskTicks = BONZO_MASK_COOLDOWN;
-            if (Dungeons.showProcTitle && !Pre4Notifier.atDev()) {
+            if (Dungeons.showProcTitle && !DeviceNotifier.at4thDev()) {
                 Misc.setTitle(Text.literal("Bonzo"));
             }
             InvincibilityDuration.proc();
         }
         if (string.equals("Second Wind Activated! Your Spirit Mask saved your life!")) {
             spiritMaskTicks = SPIRIT_MASK_COOLDOWN;
-            if (Dungeons.showProcTitle && !Pre4Notifier.atDev()) {
+            if (Dungeons.showProcTitle && !DeviceNotifier.at4thDev()) {
                 Misc.setTitle(Text.literal("Spirit"));
             }
             InvincibilityDuration.proc();
         }
         if (string.equals("Your Phoenix Pet saved you from certain death!")) {
             phoenixTicks = PHOENIX_COOLDOWN;
-            if (Dungeons.showProcTitle && !Pre4Notifier.atDev()) {
+            if (Dungeons.showProcTitle && !DeviceNotifier.at4thDev()) {
                 Misc.setTitle(Text.literal("Phoenix"));
             }
             InvincibilityDuration.proc();
diff --git a/src/main/java/blade/addon/features/dungeon/f7/location/LocationNotifier.java b/src/main/java/blade/addon/features/dungeon/f7/location/LocationNotifier.java
index 6b205cf..4e18af2 100644
--- a/src/main/java/blade/addon/features/dungeon/f7/location/LocationNotifier.java
+++ b/src/main/java/blade/addon/features/dungeon/f7/location/LocationNotifier.java
@@ -11,7 +11,6 @@ import blade.addon.utils.events.Events;
 import blade.addon.utils.rendering.RenderUtils;
 import config.practical.hud.HUDComponent;
 import net.fabricmc.fabric.api.client.event.lifecycle.v1.ClientTickEvents;
-import net.minecraft.client.MinecraftClient;
 import net.minecraft.client.gui.DrawContext;
 import net.minecraft.text.Style;
 import net.minecraft.text.Text;
@@ -63,9 +62,6 @@ public class LocationNotifier {
     }
 
     public static void render(HUDComponent component, DrawContext context) {
-        int x = component.getScaledX();
-        int y = component.getScaledY();
-
-        RenderUtils.drawCenteredText(context, MinecraftClient.getInstance().textRenderer, notification, x, y, component.getWidth());
+        RenderUtils.drawCenteredText(context, component, notification);
     }
 }
diff --git a/src/main/java/blade/addon/features/dungeon/f7/location/PositionMessage.java b/src/main/java/blade/addon/features/dungeon/f7/location/PositionMessage.java
index 17c5688..3c05ebe 100644
--- a/src/main/java/blade/addon/features/dungeon/f7/location/PositionMessage.java
+++ b/src/main/java/blade/addon/features/dungeon/f7/location/PositionMessage.java
@@ -1,5 +1,6 @@
 package blade.addon.features.dungeon.f7.location;
 
+import blade.addon.utils.Misc;
 import blade.addon.utils.config.values.Floor7;
 import blade.addon.utils.dungeon.Section;
 import net.minecraft.client.network.ClientPlayerEntity;
@@ -37,7 +38,7 @@ public class PositionMessage {
         if (sent || !inAValidSection() || !inRange(player.getEntityPos())) return;
         sent = true;
         if (Floor7.enablePositionalMessages) {
-            player.networkHandler.sendChatCommand("pc " + message);
+            Misc.executeCommand("pc " + message);
         }
     }
 
diff --git a/src/main/java/blade/addon/features/dungeon/f7/terms/Pre4Notifier.java b/src/main/java/blade/addon/features/dungeon/f7/terms/DeviceNotifier.java
similarity index 60%
rename from src/main/java/blade/addon/features/dungeon/f7/terms/Pre4Notifier.java
rename to src/main/java/blade/addon/features/dungeon/f7/terms/DeviceNotifier.java
index 034e961..6443316 100644
--- a/src/main/java/blade/addon/features/dungeon/f7/terms/Pre4Notifier.java
+++ b/src/main/java/blade/addon/features/dungeon/f7/terms/DeviceNotifier.java
@@ -10,22 +10,27 @@ import config.practical.hud.HUDComponent;
 import net.minecraft.client.MinecraftClient;
 import net.minecraft.client.gui.DrawContext;
 import net.minecraft.client.network.ClientPlayerEntity;
+import net.minecraft.entity.player.PlayerEntity;
 import net.minecraft.sound.SoundEvents;
 import net.minecraft.text.Text;
+import net.minecraft.util.math.Vec3d;
 
-public class Pre4Notifier {
+public class DeviceNotifier {
 
     private static final long TOTAL_DURATION = 1500;
 
+    private static final Vec3d SS_POSITION = new Vec3d(108, 119, 94);
+    private static final double DISTANCE = 3;
+
     private static long completedTime = 0;
     private static boolean showNotification = false;
 
 
     public static void init() {
         Events.ON_TERMINAL.register((name, action, objective, current, total) -> {
-            if (!Floor7.notifyPre4Completion) return true;
+            if (!objective.equals("device") || !EntityUtil.isClientPlayer(name)) return false;
 
-            if (objective.equals("device") && EntityUtil.isClientPlayer(name) && atDev()) {
+            if ((Floor7.notifyPre4Completion && at4thDev()) || (Floor7.notifySSCompletion && atSS())) {
                 completedTime = System.currentTimeMillis();
                 showNotification = true;
                 Scheduler.scheduleSound(SoundEvents.BLOCK_NOTE_BLOCK_PLING.value(), 1, 1);
@@ -35,14 +40,24 @@ public class Pre4Notifier {
         });
     }
 
-    public static boolean atDev() {
+    public static boolean atSS() {
+        ClientPlayerEntity player = MinecraftClient.getInstance().player;
+        if (player == null) return false;
+        return SS_POSITION.distanceTo(player.getEntityPos()) <= DISTANCE;
+    }
+
+    public static boolean atSS(PlayerEntity player) {
+        return SS_POSITION.distanceTo(player.getEntityPos()) <= DISTANCE;
+    }
+
+    public static boolean at4thDev() {
         ClientPlayerEntity player = MinecraftClient.getInstance().player;
         if (player == null) return false;
         return (player.getX() >= 63 && player.getX() <= 64 && player.getY() == 127 && player.getZ() >= 35 && player.getZ() <= 36);
     }
 
     public static boolean disableTitles() {
-        return  Floor7.disableTitlesAtPre4 && atDev() && Phase.inTerminals();
+        return ((Floor7.disableTitlesAtPre4 && at4thDev()) || (Floor7.disableTitlesAtSS && atSS())) && Phase.inTerminals();
     }
 
     public static boolean display() {
@@ -51,10 +66,6 @@ public class Pre4Notifier {
 
     public static void render(HUDComponent component, DrawContext context) {
         if (System.currentTimeMillis() - completedTime >= TOTAL_DURATION) showNotification = false;
-
-        int x = component.getScaledX();
-        int y = component.getScaledY();
-
-        RenderUtils.drawCenteredText(context, MinecraftClient.getInstance().textRenderer, Text.literal("§aDevice Completed!"), x, y, component.getWidth());
+        RenderUtils.drawCenteredText(context, component, Text.literal("§aDevice Completed!"));
     }
 }
diff --git a/src/main/java/blade/addon/features/dungeon/f7/terms/LeapNotification.java b/src/main/java/blade/addon/features/dungeon/f7/terms/LeapNotification.java
index a327cbe..1655255 100644
--- a/src/main/java/blade/addon/features/dungeon/f7/terms/LeapNotification.java
+++ b/src/main/java/blade/addon/features/dungeon/f7/terms/LeapNotification.java
@@ -7,7 +7,6 @@ import blade.addon.utils.dungeon.Phase;
 import blade.addon.utils.rendering.RenderUtils;
 import config.practical.hud.HUDComponent;
 import net.fabricmc.fabric.api.client.event.lifecycle.v1.ClientTickEvents;
-import net.minecraft.client.MinecraftClient;
 import net.minecraft.client.gui.DrawContext;
 import net.minecraft.client.network.ClientPlayerEntity;
 import net.minecraft.client.world.ClientWorld;
@@ -89,14 +88,10 @@ public class LeapNotification {
     }
 
     public static void render(HUDComponent component, DrawContext context) {
-        int x = component.getScaledX();
-        int y = component.getScaledY();
-
         String start;
         if (currentSpot == 3) start = (count > 2 ? "§9" : "§4");
         else start = (count > 3 ? "§9" : "§4");
 
-        RenderUtils.drawCenteredText(context, MinecraftClient.getInstance().textRenderer, Text.literal(start + count + "§9/4 Players Leaped"), x, y, component.getWidth());
-
+        RenderUtils.drawCenteredText(context, component, Text.literal(start + count + "§9/4 Players Leaped"));
     }
 }
diff --git a/src/main/java/blade/addon/features/dungeon/f7/terms/MelodyWarning.java b/src/main/java/blade/addon/features/dungeon/f7/terms/MelodyWarning.java
index 3c11f84..a39f286 100644
--- a/src/main/java/blade/addon/features/dungeon/f7/terms/MelodyWarning.java
+++ b/src/main/java/blade/addon/features/dungeon/f7/terms/MelodyWarning.java
@@ -10,7 +10,6 @@ import blade.addon.utils.dungeon.Phase;
 import blade.addon.utils.events.Events;
 import blade.addon.utils.rendering.RenderUtils;
 import config.practical.hud.HUDComponent;
-import net.minecraft.client.MinecraftClient;
 import net.minecraft.client.gui.DrawContext;
 import net.minecraft.text.MutableText;
 import net.minecraft.text.Style;
@@ -83,9 +82,6 @@ public class MelodyWarning {
     }
 
     public static void render(HUDComponent component, DrawContext context) {
-        int x = component.getScaledX();
-        int y = component.getScaledY();
-
         DungeonClass dungeonClass = DungeonClass.getClass(name);
         int num = Math.min(furthestProgress / 25, 3);
 
@@ -94,13 +90,12 @@ public class MelodyWarning {
             color = DungeonClass.getColor(name);
         }
 
-
         MutableText nameText = Text.literal(dungeonClass != null ? dungeonClass.name() : name != null? name: "Someone").setStyle(Style.EMPTY.withColor(color).withBold(true));
         MutableText infoText = Text.literal(" §r§dhas melody! " + num + "/4").setStyle(Style.EMPTY);
 
         Text text = nameText.append(infoText);
 
-        RenderUtils.drawCenteredText(context, MinecraftClient.getInstance().textRenderer, text, x, y, component.getWidth());
+        RenderUtils.drawCenteredText(context, component, text);
     }
 
 }
diff --git a/src/main/java/blade/addon/features/dungeon/f7/terms/SectionProgress.java b/src/main/java/blade/addon/features/dungeon/f7/terms/SectionProgress.java
new file mode 100644
index 0000000..e48c195
--- /dev/null
+++ b/src/main/java/blade/addon/features/dungeon/f7/terms/SectionProgress.java
@@ -0,0 +1,51 @@
+package blade.addon.features.dungeon.f7.terms;
+
+import blade.addon.utils.config.values.Floor7;
+import blade.addon.utils.dungeon.Phase;
+import blade.addon.utils.dungeon.Section;
+import blade.addon.utils.events.Events;
+import blade.addon.utils.rendering.RenderUtils;
+import config.practical.hud.HUDComponent;
+import net.minecraft.client.gui.DrawContext;
+import net.minecraft.text.Text;
+
+public class SectionProgress {
+
+    private static int completed = 0;
+    private static int sectionTotal = 7;
+
+    public static void init() {
+
+        Events.ON_TERMINAL.register((formattedName, action, objective, current, total) -> {
+            completed = current;
+            sectionTotal = total;
+            return false;
+        });
+
+        Events.ON_SECTION_CHANGE.register(() -> {
+            if (completed == sectionTotal) completed = 0;
+            sectionTotal = getTotal();
+            return false;
+        });
+
+        Events.ON_LOCATION_CHANGE.register(newLocation -> {
+            completed = 0;
+            sectionTotal = 7;
+            return false;
+        });
+
+    }
+
+    private static int getTotal() {
+        return Section.getSection() != 2? 7: 8;
+    }
+
+    public static boolean display() {
+        return Floor7.showSectionProgress && Phase.inTerminals();
+    }
+
+    public static void render(HUDComponent component, DrawContext context) {
+        RenderUtils.drawCenteredText(context, component, Text.literal("§a(§c" + completed + "§a/" + sectionTotal + ")"));
+    }
+
+}
diff --git a/src/main/java/blade/addon/features/dungeon/f7/terms/TermStartTimer.java b/src/main/java/blade/addon/features/dungeon/f7/terms/TermStartTimer.java
index 1d2e10a..4789a24 100644
--- a/src/main/java/blade/addon/features/dungeon/f7/terms/TermStartTimer.java
+++ b/src/main/java/blade/addon/features/dungeon/f7/terms/TermStartTimer.java
@@ -7,7 +7,6 @@ import blade.addon.utils.dungeon.Phase;
 import blade.addon.utils.events.Events;
 import blade.addon.utils.rendering.RenderUtils;
 import config.practical.hud.HUDComponent;
-import net.minecraft.client.MinecraftClient;
 import net.minecraft.client.gui.DrawContext;
 import net.minecraft.text.Text;
 
@@ -34,11 +33,7 @@ public class TermStartTimer {
     }
 
     public static void render(HUDComponent component, DrawContext context) {
-        int x = component.getScaledX();
-        int y = component.getScaledY();
-
         double num = tick * Constants.TICK_DURATION;
-        RenderUtils.drawCenteredText(context, MinecraftClient.getInstance().textRenderer, Text.literal(Constants.DECIMAL_FORMAT.format(num)), x, y, component.getWidth(), Constants.YELLOW_COLOR);
-
+        RenderUtils.drawCenteredText(context, component, Text.literal(Constants.DECIMAL_FORMAT.format(num)), Constants.YELLOW_COLOR);
     }
 }
diff --git a/src/main/java/blade/addon/features/dungeon/f7/terms/TitleHider.java b/src/main/java/blade/addon/features/dungeon/f7/terms/TitleHider.java
index 23d90cc..e0f13c5 100644
--- a/src/main/java/blade/addon/features/dungeon/f7/terms/TitleHider.java
+++ b/src/main/java/blade/addon/features/dungeon/f7/terms/TitleHider.java
@@ -18,7 +18,7 @@ public class TitleHider {
 
     public static void processSubtitle(SubtitleS2CPacket packet) {
         if (!shouldHideTitle()) return;
-        if (Section.inSection(1) && Pre4Notifier.atDev()) return;
+        if (Section.inSection(1) && DeviceNotifier.at4thDev()) return;
         String string = packet.text().getString().replaceAll("§.", "");
 
 
diff --git a/src/main/java/blade/addon/features/highlight/MobHighlight.java b/src/main/java/blade/addon/features/highlight/MobHighlight.java
index 25696a4..e87bb3d 100644
--- a/src/main/java/blade/addon/features/highlight/MobHighlight.java
+++ b/src/main/java/blade/addon/features/highlight/MobHighlight.java
@@ -30,6 +30,7 @@ import net.minecraft.text.TextColor;
 import net.minecraft.util.math.Box;
 
 import java.util.ArrayList;
+import java.util.concurrent.ConcurrentHashMap;
 import java.util.concurrent.ConcurrentLinkedQueue;
 import java.util.function.Predicate;
 
@@ -62,7 +63,6 @@ public class MobHighlight {
 
     private static final ConcurrentLinkedQueue<DataHolder> savedEntities = new ConcurrentLinkedQueue<>();
 
-
     public static boolean dontRenderHighlight = false;
     @ConfigValue
     public static boolean mobHighlight = false;
@@ -136,7 +136,6 @@ public class MobHighlight {
                     testArmourStand(armorStand, client);
                 }
             }
-
             savedEntities.removeIf(dataHolder -> dataHolder.entity.isRemoved());
         });
 
@@ -271,7 +270,7 @@ public class MobHighlight {
             case ZombieEntity ignored -> true;
             case SkeletonEntity ignored -> true;
             case WitherSkeletonEntity ignored -> true;
-            case PlayerEntity ignored -> !isMiniBoss(entity);
+            case PlayerEntity ignored -> !EntityUtil.isARealPlayer(entity) && !EntityUtil.isClientPlayer(entity) && !isMiniBoss(entity);
             default -> false;
         };
     }
diff --git a/src/main/java/blade/addon/features/highlight/TeammateHighlight.java b/src/main/java/blade/addon/features/highlight/TeammateHighlight.java
new file mode 100644
index 0000000..60db950
--- /dev/null
+++ b/src/main/java/blade/addon/features/highlight/TeammateHighlight.java
@@ -0,0 +1,72 @@
+package blade.addon.features.highlight;
+
+import blade.addon.features.dungeon.HidePlayers;
+import blade.addon.utils.Constants;
+import blade.addon.utils.Location;
+import blade.addon.utils.config.values.Dungeons;
+import blade.addon.utils.data.EntityUtil;
+import blade.addon.utils.dungeon.DungeonClass;
+import blade.addon.utils.events.Events;
+import blade.addon.utils.rendering.RenderUtils;
+import blade.addon.utils.rendering.RenderingEvents;
+import net.fabricmc.fabric.api.client.event.lifecycle.v1.ClientTickEvents;
+import net.fabricmc.fabric.api.client.rendering.v1.world.WorldRenderContext;
+import net.minecraft.client.render.VertexConsumer;
+import net.minecraft.client.util.math.MatrixStack;
+import net.minecraft.entity.Entity;
+import net.minecraft.entity.player.PlayerEntity;
+import net.minecraft.text.Text;
+import net.minecraft.util.math.Vec3d;
+
+import java.util.concurrent.ConcurrentLinkedQueue;
+
+public class TeammateHighlight {
+
+    private static final ConcurrentLinkedQueue<PlayerEntity> teammates = new ConcurrentLinkedQueue<>();
+
+    public static void init() {
+
+        Events.ON_ENTITY_TRACKED.register((entity, world) -> {
+            if (!Location.inDungeon()) return false;
+            if (entity instanceof PlayerEntity player) {
+                if (EntityUtil.isARealPlayer(player) && !EntityUtil.isClientPlayer(player) && !teammates.contains(player)) {
+                    teammates.add(player);
+                }
+            }
+            return false;
+        });
+
+        Events.ON_LOCATION_CHANGE.register(location -> {
+            teammates.clear();
+            return false;
+        });
+
+        ClientTickEvents.END_CLIENT_TICK.register(client -> teammates.removeIf(Entity::isRemoved));
+
+        RenderingEvents.NO_DEPTH_OUTLINE_ENTITY.register(TeammateHighlight::renderOutline);
+    }
+
+    private static void renderOutline(WorldRenderContext context, MatrixStack matrixStack, VertexConsumer consumer) {
+        if (!Dungeons.highlightTeammates && !Dungeons.renderClassName) return;
+
+        teammates.forEach(player -> {
+            if (Dungeons.dontHighlightHiddenTeammates && HidePlayers.shouldHidePlayers(player)) return;
+            DungeonClass clazz = DungeonClass.getClass(player);
+            if (clazz == null) return;
+
+            int color = DungeonClass.getColor(clazz);
+
+            if (Dungeons.highlightTeammates) {
+                float[] rgba = RenderUtils.toFloats(color);
+                RenderUtils.renderOutline(matrixStack, consumer, EntityUtil.getBox(player), rgba);
+            }
+
+            if (Dungeons.renderClassName) {
+                Text text = Text.literal(player.getName().getString()).withColor(color).append(Text.literal(" [" + DungeonClass.getChar(clazz) + "]").withColor(Constants.YELLOW_COLOR));
+                Vec3d pos = EntityUtil.getLerpedPos(player);
+                RenderUtils.renderText(context, matrixStack, text, pos.getX(), pos.getY() + 2.75, pos.getZ(), 2);
+            }
+        });
+    }
+
+}
diff --git a/src/main/java/blade/addon/features/highlight/WitherHighlight.java b/src/main/java/blade/addon/features/highlight/WitherHighlight.java
index b90f1b1..0f4d454 100644
--- a/src/main/java/blade/addon/features/highlight/WitherHighlight.java
+++ b/src/main/java/blade/addon/features/highlight/WitherHighlight.java
@@ -55,14 +55,14 @@ public class WitherHighlight {
     private static void renderFilled(WorldRenderContext context, MatrixStack matrixStack, VertexConsumer consumer) {
         if (!MobHighlight.mobHighlight || !MobHighlight.renderFilled() || MobHighlight.dontRenderHighlight) return;
 
-        float[] rgba = RenderUtils.toFloats(MobHighlight.batFilledColor);
+        float[] rgba = RenderUtils.toFloats(MobHighlight.witherFilledColor);
         withers.forEach(entity -> RenderUtils.renderFilled(matrixStack, consumer, getBox(entity), rgba));
     }
 
     private static void renderOutline(WorldRenderContext context, MatrixStack matrixStack, VertexConsumer consumer) {
         if (!MobHighlight.mobHighlight || !MobHighlight.renderOutline() || MobHighlight.dontRenderHighlight) return;
 
-        float[] rgba = RenderUtils.toFloats(MobHighlight.batOutlineColor);
+        float[] rgba = RenderUtils.toFloats(MobHighlight.witherOutlineColor);
         withers.forEach(entity -> RenderUtils.renderOutline(matrixStack, consumer, getBox(entity), rgba));
     }
 
diff --git a/src/main/java/blade/addon/features/item/HeldItemToolTip.java b/src/main/java/blade/addon/features/item/HeldItemToolTip.java
index 14caeea..9763df6 100644
--- a/src/main/java/blade/addon/features/item/HeldItemToolTip.java
+++ b/src/main/java/blade/addon/features/item/HeldItemToolTip.java
@@ -30,7 +30,8 @@ public class HeldItemToolTip {
     }
 
     public static void render(HUDComponent component, DrawContext context) {
-        RenderUtils.drawCenteredText(context, component, tooltip, color);
+        int currentColor = heldItemFade > 0? color: 0xffffffff;
+        RenderUtils.drawCenteredText(context, component, tooltip, currentColor);
     }
 
 }
diff --git a/src/main/java/blade/addon/features/other/KickedTimer.java b/src/main/java/blade/addon/features/other/KickedTimer.java
index d6b787e..5aaf47c 100644
--- a/src/main/java/blade/addon/features/other/KickedTimer.java
+++ b/src/main/java/blade/addon/features/other/KickedTimer.java
@@ -1,11 +1,9 @@
 package blade.addon.features.other;
 
-import blade.addon.utils.Constants;
 import blade.addon.utils.config.values.ExtraOptions;
 import blade.addon.utils.events.Events;
 import blade.addon.utils.rendering.RenderUtils;
 import config.practical.hud.HUDComponent;
-import net.minecraft.client.MinecraftClient;
 import net.minecraft.client.gui.DrawContext;
 
 public class KickedTimer {
@@ -35,12 +33,8 @@ public class KickedTimer {
     public static void render(HUDComponent component, DrawContext context) {
         long diff = System.currentTimeMillis() - kickedTime;
         if (diff > 60 * 1000) isKicked = false;
-
-        int x = component.getScaledX();
-        int y = component.getScaledY();
-
-        RenderUtils.drawCenteredText(context, MinecraftClient.getInstance().textRenderer, "§5Time kicked: §f" + Constants.DECIMAL_FORMAT.format(Math.min(diff / 1000.0, 60)) + "s", x, y, component.getWidth());
-
+        double drawnTime = Math.min(diff / 1000.0, 60.0);
+        RenderUtils.drawPrefixedTimer(component, context, drawnTime, "Time kicked");
     }
 
 }
diff --git a/src/main/java/blade/addon/features/other/SelectedPet.java b/src/main/java/blade/addon/features/other/SelectedPet.java
index 184c51a..9f6a5d3 100644
--- a/src/main/java/blade/addon/features/other/SelectedPet.java
+++ b/src/main/java/blade/addon/features/other/SelectedPet.java
@@ -1,11 +1,13 @@
 package blade.addon.features.other;
 
 import blade.addon.utils.Constants;
-import blade.addon.utils.debug.Debug;
-import blade.addon.utils.Scheduler;
+import blade.addon.utils.Misc;
 import blade.addon.utils.config.values.ExtraOptions;
+import blade.addon.utils.debug.Debug;
 import blade.addon.utils.events.Events;
+import blade.addon.utils.rendering.RenderUtils;
 import config.practical.hud.HUDComponent;
+import net.fabricmc.fabric.api.client.event.lifecycle.v1.ClientTickEvents;
 import net.fabricmc.fabric.api.client.message.v1.ClientReceiveMessageEvents;
 import net.minecraft.client.MinecraftClient;
 import net.minecraft.client.font.TextRenderer;
@@ -27,12 +29,15 @@ public class SelectedPet {
     private static final Pattern MANUAL_EQUIP_PET_PATTERN = Pattern.compile("^You (summoned|despawned) your (?:\\[Lvl \\d+] )?(?:\\[[^\\]]+\\] )?(.+)!$");
     private static final Pattern RULE_EQUIP_PET_PATTERN = Pattern.compile("^Autopet equipped your \\[Lvl \\d+] (?:\\[[^\\]]+\\] )?(.+)! VIEW RULE$");
 
+    private static final int TOTAL_TICKS = 20;
+
     private static final Text NO_PET = Text.literal("§cNo pet");
     private static final String identifierPrefix = "pets/";
 
     private static Text currentPetText = NO_PET;
     private static String currentPetString = "";
     private static Identifier spriteId = null;
+    private static int tick = 0;
 
     public static void init() {
         ClientReceiveMessageEvents.GAME.register((message, overlay) -> {
@@ -85,6 +90,11 @@ public class SelectedPet {
 
             return false;
         });
+
+        ClientTickEvents.END_WORLD_TICK.register((world -> {
+            tick = Math.max(tick - 1, 0);
+        }));
+
     }
 
     private static void despawnPet() {
@@ -107,7 +117,8 @@ public class SelectedPet {
         updateSprite(stringName);
 
         if (sendSound && ExtraOptions.sendOnPetSound) {
-            Scheduler.scheduleSound(ExtraOptions.petSound);
+            Misc.sendSound(ExtraOptions.petSound);
+            tick = TOTAL_TICKS;
         }
     }
 
@@ -152,7 +163,14 @@ public class SelectedPet {
         TextRenderer textRenderer = MinecraftClient.getInstance().textRenderer;
 
         context.drawText(textRenderer, currentPetText, x + (ExtraOptions.includePetSprite ? 18 : 0), y + component.getHeight() - textRenderer.fontHeight, 0xffffffff, true);
+    }
+
+    public static boolean displayNotification() {
+        return ExtraOptions.sendPetSwapNotification && tick > 0;
+    }
 
+    public static void renderNotification(HUDComponent component, DrawContext context) {
+        RenderUtils.drawCenteredText(context, component, currentPetText);
     }
 
 }
diff --git a/src/main/java/blade/addon/mixin/ClientConnectionMixin.java b/src/main/java/blade/addon/mixin/ClientConnectionMixin.java
index 760e6a8..fc12c4c 100644
--- a/src/main/java/blade/addon/mixin/ClientConnectionMixin.java
+++ b/src/main/java/blade/addon/mixin/ClientConnectionMixin.java
@@ -47,11 +47,8 @@ public class ClientConnectionMixin {
 
     @Inject(method = "sendImmediately", at = @At("HEAD"))
     private void sendImmediately(Packet<?> packet, ChannelFutureListener channelFutureListener, boolean flush, CallbackInfo ci) {
-        if (packet instanceof CommonPingS2CPacket common) {
-            if (common.getParameter() == 0) return;
-            Events.ON_SERVER_TICK.invoke(ServerTickEvent::onServerTick);
-        }
-
-
+        // for some reason this just being here
+        // fixes player tracking and I don't know why
+        // will hopefully look into it later
     }
 }
diff --git a/src/main/java/blade/addon/mixin/ClientPlayNetworkHandlerMixin.java b/src/main/java/blade/addon/mixin/ClientPlayNetworkHandlerMixin.java
index ddd53a6..edb13ac 100644
--- a/src/main/java/blade/addon/mixin/ClientPlayNetworkHandlerMixin.java
+++ b/src/main/java/blade/addon/mixin/ClientPlayNetworkHandlerMixin.java
@@ -1,20 +1,16 @@
 package blade.addon.mixin;
 
 import blade.addon.features.dungeon.f7.terms.TitleHider;
-import blade.addon.utils.data.EntityUtil;
-import blade.addon.utils.debug.Debug;
 import blade.addon.utils.Misc;
 import blade.addon.utils.config.values.ExtraOptions;
-import blade.addon.utils.interfaces.PlayerDataHolder;
+import blade.addon.utils.debug.Debug;
 import blade.addon.utils.events.Events;
 import com.llamalad7.mixinextras.injector.ModifyExpressionValue;
 import com.llamalad7.mixinextras.sugar.Local;
-import net.minecraft.client.MinecraftClient;
 import net.minecraft.client.network.ClientPlayNetworkHandler;
 import net.minecraft.client.network.PlayerListEntry;
 import net.minecraft.client.world.ClientWorld;
 import net.minecraft.entity.Entity;
-import net.minecraft.entity.player.PlayerEntity;
 import net.minecraft.network.packet.s2c.play.EntitySpawnS2CPacket;
 import net.minecraft.network.packet.s2c.play.EntityTrackerUpdateS2CPacket;
 import net.minecraft.network.packet.s2c.play.PlaySoundFromEntityS2CPacket;
@@ -28,7 +24,6 @@ import net.minecraft.sound.SoundEvents;
 import net.minecraft.text.Text;
 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.callback.CallbackInfo;
@@ -110,32 +105,8 @@ public class ClientPlayNetworkHandlerMixin {
 
     @Inject(method = "onEntitySpawn", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/network/ClientPlayNetworkHandler;playSpawnSound(Lnet/minecraft/entity/Entity;)V"))
     public void onEntitySpawn(EntitySpawnS2CPacket packet, CallbackInfo ci, @Local Entity entity) {
-        if (entity instanceof PlayerEntity player) {
-            PlayerDataHolder dataHolder = (PlayerDataHolder) (Object) player;
-            boolean isRealPlayer = isARealPlayer(player);
-            dataHolder.blade_addons$setIsRealPlayer(isRealPlayer);
-        }
         Events.ON_ENTITY_SPAWNED.invoke(entityTrackEvent -> entityTrackEvent.onEntity(entity, world));
     }
 
-    @Unique
-    private static boolean isARealPlayer(Entity entity) {
-        if (entity instanceof PlayerEntity player) {
-
-            if (EntityUtil.isClientPlayer(player)) return true;
-
-            ClientPlayNetworkHandler networkHandler = MinecraftClient.getInstance().getNetworkHandler();
-            if (networkHandler == null) return false;
-
-            PlayerListEntry entry = networkHandler.getPlayerListEntry(player.getUuid());
-
-            //this is a hack which will fail if someone has a really old bugged ign that includes a space
-            if (entry != null) {
-                String name = entry.getProfile().name();
-                return !name.isEmpty() && !name.contains(" ");
-            }
-        }
-        return false;
-    }
 
 }
\ No newline at end of file
diff --git a/src/main/java/blade/addon/mixin/ClientPlayerEntityMixin.java b/src/main/java/blade/addon/mixin/ClientPlayerEntityMixin.java
index 59a7cb9..0cca482 100644
--- a/src/main/java/blade/addon/mixin/ClientPlayerEntityMixin.java
+++ b/src/main/java/blade/addon/mixin/ClientPlayerEntityMixin.java
@@ -4,10 +4,7 @@ import blade.addon.features.item.DropAnimation;
 import blade.addon.features.item.ProtectItem;
 import blade.addon.utils.config.values.Dungeons;
 import com.llamalad7.mixinextras.sugar.Local;
-import com.mojang.authlib.GameProfile;
-import net.minecraft.client.network.AbstractClientPlayerEntity;
 import net.minecraft.client.network.ClientPlayerEntity;
-import net.minecraft.client.world.ClientWorld;
 import net.minecraft.item.ItemStack;
 import org.spongepowered.asm.mixin.Mixin;
 import org.spongepowered.asm.mixin.injection.At;
@@ -15,16 +12,15 @@ import org.spongepowered.asm.mixin.injection.Inject;
 import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
 
 @Mixin(ClientPlayerEntity.class)
-public class ClientPlayerEntityMixin extends AbstractClientPlayerEntity {
-
-    public ClientPlayerEntityMixin(ClientWorld world, GameProfile profile) {
-        super(world, profile);
-    }
+public class ClientPlayerEntityMixin {
 
     @Inject(method = "dropSelectedItem", at = @At("HEAD"), cancellable = true)
     public void protectItem(boolean entireStack, CallbackInfoReturnable<Boolean> cir) {
         if (Dungeons.dontProtectHeldItem && ProtectItem.isInADungeon()) return;
-        if (ProtectItem.protect(this.getInventory().getSelectedStack())) {
+
+        ClientPlayerEntity self = (ClientPlayerEntity) (Object) this;
+
+        if (ProtectItem.protect(self.getInventory().getSelectedStack())) {
             cir.setReturnValue(false);
         }
     }
@@ -32,7 +28,9 @@ public class ClientPlayerEntityMixin extends AbstractClientPlayerEntity {
     @Inject(method = "dropSelectedItem", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/network/ClientPlayNetworkHandler;sendPacket(Lnet/minecraft/network/packet/Packet;)V"))
     public void getDropped(boolean entireStack, CallbackInfoReturnable<Boolean> cir, @Local ItemStack itemStack) {
         if (!DropAnimation.shouldProceed()) return;
-        int slot = getInventory().getSelectedSlot();
+
+        ClientPlayerEntity self = (ClientPlayerEntity) (Object) this;
+        int slot = self.getInventory().getSelectedSlot();
         DropAnimation.setDroppedData(itemStack, slot);
     }
 }
diff --git a/src/main/java/blade/addon/mixin/DrawContextMixin.java b/src/main/java/blade/addon/mixin/DrawContextMixin.java
index 2f77ea3..4077dfe 100644
--- a/src/main/java/blade/addon/mixin/DrawContextMixin.java
+++ b/src/main/java/blade/addon/mixin/DrawContextMixin.java
@@ -2,16 +2,49 @@ package blade.addon.mixin;
 
 import blade.addon.utils.config.values.Visual;
 import net.minecraft.client.gui.DrawContext;
+import net.minecraft.entity.LivingEntity;
+import net.minecraft.item.ItemStack;
+import net.minecraft.item.Items;
+import net.minecraft.world.World;
+import org.joml.Matrix3x2fStack;
+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.ModifyVariable;
+import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
 
 @Mixin(DrawContext.class)
 public class DrawContextMixin {
 
-   @ModifyVariable(method = "drawCooldownProgress", at=@At("STORE"), ordinal = 0)
+    @Final
+    @Shadow
+    private Matrix3x2fStack matrices;
+
+
+    @ModifyVariable(method = "drawCooldownProgress", at=@At("STORE"), ordinal = 0)
     private float noCooldown(float f) {
         return Visual.hideCooldown? 0: f;
     }
 
+    @Inject(method = "drawItem(Lnet/minecraft/entity/LivingEntity;Lnet/minecraft/world/World;Lnet/minecraft/item/ItemStack;III)V", at=@At("HEAD"))
+    private void scaleUp(LivingEntity entity, World world, ItemStack stack, int x, int y, int seed, CallbackInfo ci) {
+        if (Visual.oldPlayerHead && stack.getItem() == Items.PLAYER_HEAD) {
+            float scale = 0.875f;
+            int offset = (16 - (int)(scale * 16)) / 2;
+
+            matrices.pushMatrix();
+            matrices.translate(x * (1 - scale) + offset, y * (1 - scale) + offset);
+            matrices.scale(scale);
+        }
+    }
+
+    @Inject(method = "drawItem(Lnet/minecraft/entity/LivingEntity;Lnet/minecraft/world/World;Lnet/minecraft/item/ItemStack;III)V", at=@At("TAIL"))
+    private void scaleDown(LivingEntity entity, World world, ItemStack stack, int x, int y, int seed, CallbackInfo ci) {
+        if (Visual.oldPlayerHead && stack.getItem() == Items.PLAYER_HEAD) {
+            matrices.popMatrix();
+        }
+    }
+
 }
diff --git a/src/main/java/blade/addon/mixin/EntityRendererMixin.java b/src/main/java/blade/addon/mixin/EntityRendererMixin.java
index 5648763..afd07fd 100644
--- a/src/main/java/blade/addon/mixin/EntityRendererMixin.java
+++ b/src/main/java/blade/addon/mixin/EntityRendererMixin.java
@@ -1,16 +1,15 @@
 package blade.addon.mixin;
 
+import blade.addon.utils.Location;
 import blade.addon.utils.config.values.Dungeons;
 import blade.addon.utils.config.values.Visual;
 import blade.addon.utils.data.EntityUtil;
-import net.minecraft.client.render.command.OrderedRenderCommandQueue;
+import blade.addon.utils.dungeon.DungeonClass;
+import net.minecraft.client.network.OtherClientPlayerEntity;
 import net.minecraft.client.render.entity.EntityRenderer;
 import net.minecraft.client.render.entity.state.EntityRenderState;
-import net.minecraft.client.render.state.CameraRenderState;
-import net.minecraft.client.util.math.MatrixStack;
 import net.minecraft.entity.Entity;
 import net.minecraft.entity.player.PlayerEntity;
-import net.minecra
…… diff 过长已截断 ……
- changed notifications a bit - added invincibility duration timer - made rendering a bit more straight forward - fixed bat not getting highlightGabriel Ellnebrand · 2026-01-20 · 32fafe4
查看变更
gradle.properties                                                                      |   2 +-
 src/main/java/blade/addon/Bladeaddons.java                                             |   5 ++++
 src/main/java/blade/addon/features/dungeon/ItemHighlight.java                          |  47 +++++++++++++---------------------
 src/main/java/blade/addon/features/dungeon/f7/BossWaypoints.java                       |  41 +++++-------------------------
 src/main/java/blade/addon/features/dungeon/f7/RelicTimer.java                          |  38 +++++++++-------------------
 src/main/java/blade/addon/features/dungeon/f7/invincibility/InvincibilityDuration.java |  32 +++++++++++++++++++++++
 src/main/java/blade/addon/features/dungeon/f7/invincibility/InvincibilityTimer.java    |   3 +++
 src/main/java/blade/addon/features/dungeon/f7/location/PositionMessages.java           |  26 ++++---------------
 src/main/java/blade/addon/features/highlight/BatHighlight.java                         |  75 ++++++++----------------------------------------------
 src/main/java/blade/addon/features/highlight/MimicHighlight.java                       |  63 ++++++++++-----------------------------------
 src/main/java/blade/addon/features/highlight/MobHighlight.java                         |  90 ++++++++++++++++-------------------------------------------------
 src/main/java/blade/addon/features/highlight/SheepHighlight.java                       |  85 +++++++++++--------------------------------------------------
 src/main/java/blade/addon/features/highlight/WitherHighlight.java                      |  80 +++++++++++----------------------------------------------
 src/main/java/blade/addon/features/item/HeldItemToolTip.java                           |  36 ++++++++++++++++++++++++++
 src/main/java/blade/addon/features/notifications/Notification.java                     |  15 +++++++++--
 src/main/java/blade/addon/features/notifications/NotificationEditScreen.java           |   1 +
 src/main/java/blade/addon/features/notifications/NotificationEntry.java                |  35 +++++++++++++++++--------
 src/main/java/blade/addon/features/notifications/Notifications.java                    |  10 ++++----
 src/main/java/blade/addon/features/other/PracticeSS.java                               |  32 ++++++++---------------
 src/main/java/blade/addon/mixin/InGameHudMixin.java                                    |  23 +++++++++++++++++
 src/main/java/blade/addon/utils/Constants.java                                         |   2 +-
 src/main/java/blade/addon/utils/config/Config.java                                     |   2 ++
 src/main/java/blade/addon/utils/config/components/Components.java                      |   8 ++++++
 src/main/java/blade/addon/utils/config/values/Dungeons.java                            |   3 +++
 src/main/java/blade/addon/utils/config/values/ExtraOptions.java                        |   3 +++
 src/main/java/blade/addon/utils/data/EntityUtil.java                                   |  13 ++++++++++
 src/main/java/blade/addon/utils/rendering/RenderHandler.java                           |  22 ++++++++++++++++
 src/main/java/blade/addon/utils/rendering/RenderUtils.java                             |  18 +++++++++++++
 src/main/java/blade/addon/utils/rendering/RenderingEvent.java                          |  10 ++++++++
 src/main/java/blade/addon/utils/rendering/RenderingEvents.java                         |  88 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 src/main/resources/assets/blade-addons/textures/gui/sprites/cross.png                  | Bin 0 -> 653 bytes
 31 files changed, 439 insertions(+), 469 deletions(-)
diff --git a/gradle.properties b/gradle.properties
index 2791ec8..127722c 100644
--- a/gradle.properties
+++ b/gradle.properties
@@ -20,6 +20,6 @@ archives_base_name=blade-addons
 # Dependencies
 fabric_version=0.138.4+1.21.10
 # I love dependencies sometimes breaking
-practical_config=74a456703958c0a6381956ccf9be4a5e8dd7d84f
+practical_config=1.4.1
 modmenu_version = 16.0.0-rc.2
 hypixel_api_version = 1.0.1
\ No newline at end of file
diff --git a/src/main/java/blade/addon/Bladeaddons.java b/src/main/java/blade/addon/Bladeaddons.java
index ef80bf6..ae48c80 100644
--- a/src/main/java/blade/addon/Bladeaddons.java
+++ b/src/main/java/blade/addon/Bladeaddons.java
@@ -18,6 +18,7 @@ import blade.addon.features.dungeon.f7.PillarExplode;
 import blade.addon.features.dungeon.f7.PredevTimer;
 import blade.addon.features.dungeon.f7.RelicTimer;
 import blade.addon.features.dungeon.f7.StormTickTimer;
+import blade.addon.features.dungeon.f7.invincibility.InvincibilityDuration;
 import blade.addon.features.dungeon.f7.invincibility.InvincibilityTimer;
 import blade.addon.features.dungeon.f7.location.LocationNotifier;
 import blade.addon.features.dungeon.f7.location.PositionMessages;
@@ -53,6 +54,7 @@ import blade.addon.utils.dungeon.DungeonClass;
 import blade.addon.utils.dungeon.Phase;
 import blade.addon.utils.dungeon.Section;
 import blade.addon.utils.events.CustomEvents;
+import blade.addon.utils.rendering.RenderingEvents;
 import net.fabricmc.api.ModInitializer;
 
 public class Bladeaddons implements ModInitializer {
@@ -72,6 +74,8 @@ public class Bladeaddons implements ModInitializer {
         DungeonClass.init();
         PartyUtil.init();
 
+        RenderingEvents.init();
+
         StormTickTimer.init();
         GoldorTickTimer.init();
         TermStartTimer.init();
@@ -112,5 +116,6 @@ public class Bladeaddons implements ModInitializer {
         Notifications.init();
         BatHighlight.init();
         WitherHighlight.init();
+        InvincibilityDuration.init();
     }
 }
\ No newline at end of file
diff --git a/src/main/java/blade/addon/features/dungeon/ItemHighlight.java b/src/main/java/blade/addon/features/dungeon/ItemHighlight.java
index 6b2990a..6ceb069 100644
--- a/src/main/java/blade/addon/features/dungeon/ItemHighlight.java
+++ b/src/main/java/blade/addon/features/dungeon/ItemHighlight.java
@@ -4,15 +4,14 @@ import blade.addon.utils.Constants;
 import blade.addon.utils.Location;
 import blade.addon.utils.config.values.Dungeons;
 import blade.addon.utils.dungeon.Phase;
-import blade.addon.utils.rendering.RenderLayers;
 import blade.addon.utils.rendering.RenderUtils;
+import blade.addon.utils.rendering.RenderingEvents;
 import net.fabricmc.fabric.api.client.event.lifecycle.v1.ClientEntityEvents;
 import net.fabricmc.fabric.api.client.event.lifecycle.v1.ClientTickEvents;
-import net.fabricmc.fabric.api.client.rendering.v1.world.WorldRenderEvents;
+import net.fabricmc.fabric.api.client.rendering.v1.world.WorldRenderContext;
 import net.minecraft.client.MinecraftClient;
 import net.minecraft.client.network.ClientPlayerEntity;
 import net.minecraft.client.render.VertexConsumer;
-import net.minecraft.client.render.VertexConsumerProvider;
 import net.minecraft.client.render.VertexRendering;
 import net.minecraft.client.util.math.MatrixStack;
 import net.minecraft.client.world.ClientWorld;
@@ -21,7 +20,6 @@ import net.minecraft.entity.ItemEntity;
 import net.minecraft.text.Text;
 import net.minecraft.util.math.Box;
 import net.minecraft.util.math.MathHelper;
-import net.minecraft.util.math.Vec3d;
 
 import java.util.HashSet;
 import java.util.List;
@@ -57,32 +55,23 @@ public class ItemHighlight {
             }
         });
 
-        WorldRenderEvents.BEFORE_DEBUG_RENDER.register(context -> {
-            if (!Dungeons.highlightItems) return;
-            Vec3d camera = context.worldState().cameraRenderState.pos;
-            MatrixStack matrices = context.matrices();
-            if (matrices == null) return;
-            matrices.push();
-            matrices.translate(-camera.x, -camera.y, -camera.z);
-
-            VertexConsumerProvider consumers = context.consumers();
-            if (consumers == null) return;
-            VertexConsumer buffer = consumers.getBuffer(RenderLayers.THROUGH_WALL_FILLED_LAYER);
-
-            double tickProgress = MinecraftClient.getInstance().getRenderTickCounter().getTickProgress(false);
-
-            trackedItems.forEach((itemEntity, integer) -> {
-                double x =  MathHelper.lerp(tickProgress, itemEntity.lastRenderX, itemEntity.getX());
-                double y =  MathHelper.lerp(tickProgress, itemEntity.lastRenderY, itemEntity.getY());
-                double z =  MathHelper.lerp(tickProgress, itemEntity.lastRenderZ, itemEntity.getZ());
-
-                EntityDimensions dimension = itemEntity.getDimensions(itemEntity.getPose());
-                Box box = dimension.getBoxAt(x, y, z).expand(0.1).offset(0, 0.05, 0);
-                float[] color = RenderUtils.toFloats(getColor(itemEntity));
-                VertexRendering.drawFilledBox(matrices, buffer, box.minX, box.minY, box.minZ, box.maxX, box.maxY, box.maxZ, color[0], color[1], color[2], color[3]);
-            });
+        RenderingEvents.NO_DEPTH_FILLED.register(ItemHighlight::render);
+    }
+
+    private static void render(WorldRenderContext context, MatrixStack matrixStack, VertexConsumer consumer) {
+        if (!Dungeons.highlightItems) return;
+        double tickProgress = MinecraftClient.getInstance().getRenderTickCounter().getTickProgress(false);
+
+
+        trackedItems.forEach((itemEntity, integer) -> {
+            double x =  MathHelper.lerp(tickProgress, itemEntity.lastRenderX, itemEntity.getX());
+            double y =  MathHelper.lerp(tickProgress, itemEntity.lastRenderY, itemEntity.getY());
+            double z =  MathHelper.lerp(tickProgress, itemEntity.lastRenderZ, itemEntity.getZ());
 
-            matrices.pop();
+            EntityDimensions dimension = itemEntity.getDimensions(itemEntity.getPose());
+            Box box = dimension.getBoxAt(x, y, z).expand(0.1).offset(0, 0.05, 0);
+            float[] color = RenderUtils.toFloats(getColor(itemEntity));
+            VertexRendering.drawFilledBox(matrixStack, consumer, box.minX, box.minY, box.minZ, box.maxX, box.maxY, box.maxZ, color[0], color[1], color[2], color[3]);
         });
     }
 
diff --git a/src/main/java/blade/addon/features/dungeon/f7/BossWaypoints.java b/src/main/java/blade/addon/features/dungeon/f7/BossWaypoints.java
index 0ae44a9..5e9aee9 100644
--- a/src/main/java/blade/addon/features/dungeon/f7/BossWaypoints.java
+++ b/src/main/java/blade/addon/features/dungeon/f7/BossWaypoints.java
@@ -7,17 +7,15 @@ import blade.addon.utils.config.FolderUtility;
 import blade.addon.utils.config.values.Floor7;
 import blade.addon.utils.data.EntityUtil;
 import blade.addon.utils.dungeon.Phase;
-import blade.addon.utils.rendering.RenderLayers;
 import blade.addon.utils.rendering.RenderUtils;
+import blade.addon.utils.rendering.RenderingEvents;
 import com.google.gson.Gson;
 import com.google.gson.JsonArray;
 import com.google.gson.JsonElement;
 import com.google.gson.JsonObject;
 import net.fabricmc.fabric.api.client.rendering.v1.world.WorldRenderContext;
-import net.fabricmc.fabric.api.client.rendering.v1.world.WorldRenderEvents;
 import net.fabricmc.fabric.api.event.player.UseBlockCallback;
 import net.minecraft.client.render.VertexConsumer;
-import net.minecraft.client.render.VertexConsumerProvider;
 import net.minecraft.client.util.math.MatrixStack;
 import net.minecraft.entity.player.PlayerEntity;
 import net.minecraft.text.Text;
@@ -25,7 +23,6 @@ import net.minecraft.util.ActionResult;
 import net.minecraft.util.Hand;
 import net.minecraft.util.hit.BlockHitResult;
 import net.minecraft.util.math.BlockPos;
-import net.minecraft.util.math.Vec3d;
 import net.minecraft.world.World;
 
 import java.io.FileWriter;
@@ -47,8 +44,8 @@ public class BossWaypoints {
     public static void init() {
         load();
         //needs separate consumers because of depth checking or smth
-        WorldRenderEvents.BEFORE_ENTITIES.register(BossWaypoints::renderThroughWall);
-        WorldRenderEvents.BEFORE_ENTITIES.register(BossWaypoints::render);
+        RenderingEvents.FILLED_BLOCK.register(BossWaypoints::render);
+        RenderingEvents.NO_DEPTH_FILLED.register(BossWaypoints::renderThroughWall);
         UseBlockCallback.EVENT.register(BossWaypoints::onBlock);
     }
 
@@ -138,48 +135,24 @@ public class BossWaypoints {
         return ActionResult.PASS;
     }
 
-    private static void renderThroughWall(WorldRenderContext context) {
+    private static void render(WorldRenderContext context, MatrixStack matrixStack, VertexConsumer consumer) {
         if (!isInValidArea()) return;
 
-        Vec3d camera = context.worldState().cameraRenderState.pos;
-        MatrixStack matrices = context.matrices();
-        if (matrices == null) return;
-        matrices.push();
-        matrices.translate(-camera.x, -camera.y, -camera.z);
-
-        VertexConsumerProvider consumers = context.consumers();
-        if (consumers == null) return;
-        VertexConsumer consumer = consumers.getBuffer(RenderLayers.FILLED_LAYER);
-
         waypoints.forEach(waypoint -> {
             if (waypoint.isThroughWall()) {
-                waypoint.Render(consumer, matrices);
+                waypoint.Render(consumer, matrixStack);
             }
         });
-        matrices.pop();
-
     }
 
-    private static void render(WorldRenderContext context) {
+    private static void renderThroughWall(WorldRenderContext context, MatrixStack matrixStack, VertexConsumer consumer) {
         if (!isInValidArea()) return;
-        Vec3d camera = context.worldState().cameraRenderState.pos;
-
-        MatrixStack matrices = context.matrices();
-        if (matrices == null) return;
-        matrices.push();
-        matrices.translate(-camera.x, -camera.y, -camera.z);
-
-        VertexConsumerProvider consumers = context.consumers();
-        if (consumers == null) return;
-        VertexConsumer consumer = consumers.getBuffer(RenderLayers.THROUGH_WALL_FILLED_LAYER);
 
         waypoints.forEach(waypoint -> {
             if (!waypoint.isThroughWall()) {
-                waypoint.Render(consumer, matrices);
+                waypoint.Render(consumer, matrixStack);
             }
         });
-        matrices.pop();
-
     }
 
     public static boolean waypointExists(double x, double y, double z) {
diff --git a/src/main/java/blade/addon/features/dungeon/f7/RelicTimer.java b/src/main/java/blade/addon/features/dungeon/f7/RelicTimer.java
index 6b2fa42..bdd5ec7 100644
--- a/src/main/java/blade/addon/features/dungeon/f7/RelicTimer.java
+++ b/src/main/java/blade/addon/features/dungeon/f7/RelicTimer.java
@@ -9,17 +9,16 @@ import blade.addon.utils.data.ItemUtil;
 import blade.addon.utils.debug.Debug;
 import blade.addon.utils.dungeon.Phase;
 import blade.addon.utils.events.Events;
-import blade.addon.utils.rendering.RenderLayers;
 import blade.addon.utils.rendering.RenderUtils;
+import blade.addon.utils.rendering.RenderingEvents;
 import blade.addon.utils.times.PersonalBests;
 import config.practical.hud.HUDComponent;
 import net.fabricmc.fabric.api.client.event.lifecycle.v1.ClientTickEvents;
-import net.fabricmc.fabric.api.client.rendering.v1.world.WorldRenderEvents;
+import net.fabricmc.fabric.api.client.rendering.v1.world.WorldRenderContext;
 import net.minecraft.client.MinecraftClient;
 import net.minecraft.client.gui.DrawContext;
 import net.minecraft.client.network.ClientPlayerEntity;
 import net.minecraft.client.render.VertexConsumer;
-import net.minecraft.client.render.VertexConsumerProvider;
 import net.minecraft.client.render.VertexRendering;
 import net.minecraft.client.util.math.MatrixStack;
 import net.minecraft.entity.Entity;
@@ -30,7 +29,6 @@ import net.minecraft.text.Text;
 import net.minecraft.util.Formatting;
 import net.minecraft.util.math.BlockPos;
 import net.minecraft.util.math.Box;
-import net.minecraft.util.math.Vec3d;
 
 import java.util.regex.Matcher;
 import java.util.regex.Pattern;
@@ -133,7 +131,7 @@ public class RelicTimer {
                     double az = armorStand.getZ();
 
                     for (Relic relic : Relic.values()) {
-                        if (relic.placedTime != 0 && Misc.getDistance(relic.box.maxX, ax, relic.box.maxZ, az) < 1) {
+                        if (relic.placedTime != 0 && Misc.getDistance(relic.box.maxX, relic.box.maxZ, ax, az) < 1) {
                             relic.placedTime = System.currentTimeMillis();
                             break;
                         }
@@ -195,27 +193,7 @@ public class RelicTimer {
             return false;
         });
 
-        WorldRenderEvents.BEFORE_DEBUG_RENDER.register(context -> {
-            if (pickedupRelic == null || !Floor7.renderRelicHighlight) return;
-
-            Vec3d camera = context.worldState().cameraRenderState.pos;
-            MatrixStack matrices = context.matrices();
-            if (matrices == null) return;
-            matrices.push();
-            matrices.translate(-camera.x, -camera.y, -camera.z);
-
-            VertexConsumerProvider consumers = context.consumers();
-            if (consumers == null) return;
-            VertexConsumer buffer = consumers.getBuffer(RenderLayers.FILLED_LAYER);
-
-
-            Box box = pickedupRelic.box;
-            float[] color = RenderUtils.toFloats(pickedupRelic.color);
-            VertexRendering.drawFilledBox(matrices, buffer, box.minX, box.minY, box.minZ, box.maxX, box.maxY, box.maxZ, color[0], color[1], color[2], color[3]);
-
-
-            matrices.pop();
-        });
+        RenderingEvents.FILLED_BLOCK.register(RelicTimer::worldRender);
 
     }
 
@@ -256,6 +234,14 @@ public class RelicTimer {
 
     }
 
+    private static void worldRender(WorldRenderContext context, MatrixStack matrixStack, VertexConsumer consumer) {
+        if (pickedupRelic == null || !Floor7.renderRelicHighlight) return;
+
+        Box box = pickedupRelic.box;
+        float[] color = RenderUtils.toFloats(pickedupRelic.color);
+        VertexRendering.drawFilledBox(matrixStack, consumer, box.minX, box.minY, box.minZ, box.maxX, box.maxY, box.maxZ, color[0], color[1], color[2], color[3]);
+    }
+
     public static boolean display() {
         return Floor7.enableRelicStartTimer && Location.inDungeon() && Phase.inP5() && tick > -1 && !Floor7.replaceWithProgressBar;
     }
diff --git a/src/main/java/blade/addon/features/dungeon/f7/invincibility/InvincibilityDuration.java b/src/main/java/blade/addon/features/dungeon/f7/invincibility/InvincibilityDuration.java
new file mode 100644
index 0000000..254260d
--- /dev/null
+++ b/src/main/java/blade/addon/features/dungeon/f7/invincibility/InvincibilityDuration.java
@@ -0,0 +1,32 @@
+package blade.addon.features.dungeon.f7.invincibility;
+
+import blade.addon.utils.config.values.Dungeons;
+import blade.addon.utils.events.Events;
+import blade.addon.utils.rendering.RenderUtils;
+import config.practical.hud.HUDComponent;
+import net.minecraft.client.gui.DrawContext;
+
+public class InvincibilityDuration {
+
+    private static final int MAX_DURATION = 3 * 20;
+    private static int ticks = 0;
+
+    public static void init() {
+        Events.ON_SERVER_TICK.register(() -> {
+            ticks = Math.max(0, ticks - 1);
+            return false;
+        });
+    }
+
+    public static void proc() {
+        ticks = MAX_DURATION;
+    }
+
+    public static boolean display() {
+        return  ticks > 0 && Dungeons.InvincibilityDuration;
+    }
+
+    public static void render(HUDComponent component, DrawContext context) {
+        RenderUtils.drawTimer(component, context, ticks, 0xffffffff);
+    }
+}
diff --git a/src/main/java/blade/addon/features/dungeon/f7/invincibility/InvincibilityTimer.java b/src/main/java/blade/addon/features/dungeon/f7/invincibility/InvincibilityTimer.java
index 6f875f0..208cf43 100644
--- a/src/main/java/blade/addon/features/dungeon/f7/invincibility/InvincibilityTimer.java
+++ b/src/main/java/blade/addon/features/dungeon/f7/invincibility/InvincibilityTimer.java
@@ -114,18 +114,21 @@ public class InvincibilityTimer {
             if (Dungeons.showProcTitle && !Pre4Notifier.atDev()) {
                 Misc.setTitle(Text.literal("Bonzo"));
             }
+            InvincibilityDuration.proc();
         }
         if (string.equals("Second Wind Activated! Your Spirit Mask saved your life!")) {
             spiritMaskTicks = SPIRIT_MASK_COOLDOWN;
             if (Dungeons.showProcTitle && !Pre4Notifier.atDev()) {
                 Misc.setTitle(Text.literal("Spirit"));
             }
+            InvincibilityDuration.proc();
         }
         if (string.equals("Your Phoenix Pet saved you from certain death!")) {
             phoenixTicks = PHOENIX_COOLDOWN;
             if (Dungeons.showProcTitle && !Pre4Notifier.atDev()) {
                 Misc.setTitle(Text.literal("Phoenix"));
             }
+            InvincibilityDuration.proc();
         }
     }
 
diff --git a/src/main/java/blade/addon/features/dungeon/f7/location/PositionMessages.java b/src/main/java/blade/addon/features/dungeon/f7/location/PositionMessages.java
index 9be2854..789c449 100644
--- a/src/main/java/blade/addon/features/dungeon/f7/location/PositionMessages.java
+++ b/src/main/java/blade/addon/features/dungeon/f7/location/PositionMessages.java
@@ -1,22 +1,19 @@
 package blade.addon.features.dungeon.f7.location;
 
-import blade.addon.utils.debug.Debug;
 import blade.addon.utils.Location;
 import blade.addon.utils.config.values.Floor7;
+import blade.addon.utils.debug.Debug;
 import blade.addon.utils.dungeon.Phase;
 import blade.addon.utils.events.Events;
-import blade.addon.utils.rendering.RenderLayers;
+import blade.addon.utils.rendering.RenderingEvents;
 import net.fabricmc.fabric.api.client.event.lifecycle.v1.ClientTickEvents;
 import net.fabricmc.fabric.api.client.rendering.v1.world.WorldRenderContext;
-import net.fabricmc.fabric.api.client.rendering.v1.world.WorldRenderEvents;
 import net.minecraft.client.MinecraftClient;
 import net.minecraft.client.network.ClientPlayerEntity;
 import net.minecraft.client.render.VertexConsumer;
-import net.minecraft.client.render.VertexConsumerProvider;
 import net.minecraft.client.render.VertexRendering;
 import net.minecraft.client.util.math.MatrixStack;
 import net.minecraft.util.math.Box;
-import net.minecraft.util.math.Vec3d;
 
 import java.util.ArrayList;
 
@@ -37,7 +34,7 @@ public class PositionMessages {
 
     public static void init() {
         ClientTickEvents.END_CLIENT_TICK.register(PositionMessages::tick);
-        WorldRenderEvents.BEFORE_ENTITIES.register(PositionMessages::render);
+        RenderingEvents.FILLED_BLOCK.register(PositionMessages::render);
         Events.ON_LOCATION_CHANGE.register(newLocation -> {
             disableAll();
             return false;
@@ -98,19 +95,10 @@ public class PositionMessages {
         }
     }
 
-    private static void render(WorldRenderContext context) {
+    private static void render(WorldRenderContext context, MatrixStack matrixStack, VertexConsumer consumer) {
         if (!Location.inDungeon() || !Debug.renderPositions) return;
 
-        Vec3d camera = context.worldState().cameraRenderState.pos;
-        MatrixStack matrices = context.matrices();
-        if (matrices == null) return;
-        matrices.push();
-        matrices.translate(-camera.x, -camera.y, -camera.z);
-
-        VertexConsumerProvider consumers = context.consumers();
-        if (consumers == null) return;
-        VertexConsumer consumer = consumers.getBuffer(RenderLayers.OUTLINE_LAYER);
-        MatrixStack.Entry entry = matrices.peek();
+        MatrixStack.Entry entry = matrixStack.peek();
         for (PositionMessage positionMessage : positionMessages) {
             Box box = positionMessage.getBox();
             if (positionMessage.sent()) {
@@ -119,9 +107,5 @@ public class PositionMessages {
                 VertexRendering.drawBox(entry, consumer, box, 1, 0, 0, 1);
             }
         }
-
-
-        matrices.pop();
-
     }
 }
diff --git a/src/main/java/blade/addon/features/highlight/BatHighlight.java b/src/main/java/blade/addon/features/highlight/BatHighlight.java
index 80e1d89..da8f986 100644
--- a/src/main/java/blade/addon/features/highlight/BatHighlight.java
+++ b/src/main/java/blade/addon/features/highlight/BatHighlight.java
@@ -1,22 +1,15 @@
 package blade.addon.features.highlight;
 
 import blade.addon.utils.Location;
-import blade.addon.utils.Misc;
+import blade.addon.utils.data.EntityUtil;
 import blade.addon.utils.events.Events;
-import blade.addon.utils.rendering.RenderLayers;
 import blade.addon.utils.rendering.RenderUtils;
+import blade.addon.utils.rendering.RenderingEvents;
 import net.fabricmc.fabric.api.client.event.lifecycle.v1.ClientTickEvents;
 import net.fabricmc.fabric.api.client.rendering.v1.world.WorldRenderContext;
-import net.fabricmc.fabric.api.client.rendering.v1.world.WorldRenderEvents;
-import net.minecraft.client.MinecraftClient;
 import net.minecraft.client.render.VertexConsumer;
-import net.minecraft.client.render.VertexConsumerProvider;
-import net.minecraft.client.render.VertexRendering;
 import net.minecraft.client.util.math.MatrixStack;
-import net.minecraft.entity.EntityDimensions;
 import net.minecraft.entity.passive.BatEntity;
-import net.minecraft.util.math.Box;
-import net.minecraft.util.math.Vec3d;
 
 import java.util.concurrent.ConcurrentLinkedQueue;
 
@@ -47,70 +40,22 @@ public class BatHighlight {
 
         ClientTickEvents.END_CLIENT_TICK.register(client -> bats.removeIf(bat -> bat.isRemoved() ||bat.isDead()));
 
-        WorldRenderEvents.AFTER_ENTITIES.register(BatHighlight::renderFilled);
-        WorldRenderEvents.AFTER_ENTITIES.register(BatHighlight::renderOutline);
+        RenderingEvents.FILLED_ENTITY.register(BatHighlight::renderFilled);
+        RenderingEvents.OUTLINE_ENTITY.register(BatHighlight::renderOutline);
     }
 
-    private static void renderFilled(WorldRenderContext context) {
-        if (!MobHighlight.mobHighlight) return;
-        if (!MobHighlight.renderFilled()) return;
-        Vec3d camera = context.worldState().cameraRenderState.pos;
-        MatrixStack matrices = context.matrices();
-        if (matrices == null) return;
-        matrices.push();
-        matrices.translate(-camera.x, -camera.y, -camera.z);
-
-        VertexConsumerProvider consumers = context.consumers();
-        if (consumers == null) return;
-        VertexConsumer filledConsumer = consumers.getBuffer(RenderLayers.FILLED_ENTITY_LAYER);
-
-        double tickProgress = MinecraftClient.getInstance().getRenderTickCounter().getTickProgress(false);
+    private static void renderFilled(WorldRenderContext context, MatrixStack matrixStack, VertexConsumer consumer) {
+        if (!MobHighlight.mobHighlight || !MobHighlight.renderFilled()) return;
 
         float[] rgba = RenderUtils.toFloats(MobHighlight.batFilledColor);
-        bats.forEach(entity -> {
-
-            Vec3d pos = Misc.getPos(entity, tickProgress);
-
-            EntityDimensions dimension = entity.getDimensions(entity.getPose());
-            Box box = dimension.getBoxAt(pos);
-
-            VertexRendering.drawFilledBox(matrices, filledConsumer, box.minX, box.minY, box.minZ, box.maxX, box.maxY, box.maxZ, rgba[0], rgba[1], rgba[2], rgba[3]);
-        });
-
-        matrices.pop();
-
+        bats.forEach(entity -> RenderUtils.renderFilled(matrixStack, consumer, EntityUtil.getBox(entity), rgba));
     }
 
-    private static void renderOutline(WorldRenderContext context) {
-        if (!MobHighlight.mobHighlight) return;
-        if (!MobHighlight.renderOutline()) return;
-        Vec3d camera = context.worldState().cameraRenderState.pos;
-        MatrixStack matrices = context.matrices();
-        if (matrices == null) return;
-        matrices.push();
-        matrices.translate(-camera.x, -camera.y, -camera.z);
-
-        VertexConsumerProvider consumers = context.consumers();
-        if (consumers == null) return;
-        VertexConsumer outlineConsumer = consumers.getBuffer(RenderLayers.getOutline(MobHighlight.outlineWidth));
+    private static void renderOutline(WorldRenderContext context, MatrixStack matrixStack, VertexConsumer consumer) {
+        if (!MobHighlight.mobHighlight || !MobHighlight.renderOutline()) return;
 
-        double tickProgress = MinecraftClient.getInstance().getRenderTickCounter().getTickProgress(false);
-
-        MatrixStack.Entry entry = matrices.peek();
         float[] rgba = RenderUtils.toFloats(MobHighlight.batOutlineColor);
-
-        bats.forEach(entity -> {
-
-            Vec3d pos = Misc.getPos(entity, tickProgress);
-
-            EntityDimensions dimension = entity.getDimensions(entity.getPose());
-            Box box = dimension.getBoxAt(pos);
-
-            VertexRendering.drawBox(entry, outlineConsumer, box, rgba[0], rgba[1], rgba[2], rgba[3]);
-        });
-
-        matrices.pop();
-
+        bats.forEach(entity -> RenderUtils.renderOutline(matrixStack, consumer, EntityUtil.getBox(entity), rgba));
     }
 
 }
diff --git a/src/main/java/blade/addon/features/highlight/MimicHighlight.java b/src/main/java/blade/addon/features/highlight/MimicHighlight.java
index cf5d258..5275f88 100644
--- a/src/main/java/blade/addon/features/highlight/MimicHighlight.java
+++ b/src/main/java/blade/addon/features/highlight/MimicHighlight.java
@@ -2,19 +2,15 @@ package blade.addon.features.highlight;
 
 import blade.addon.utils.Location;
 import blade.addon.utils.events.Events;
-import blade.addon.utils.rendering.RenderLayers;
 import blade.addon.utils.rendering.RenderUtils;
+import blade.addon.utils.rendering.RenderingEvents;
 import net.fabricmc.fabric.api.client.event.lifecycle.v1.ClientTickEvents;
 import net.fabricmc.fabric.api.client.rendering.v1.world.WorldRenderContext;
-import net.fabricmc.fabric.api.client.rendering.v1.world.WorldRenderEvents;
 import net.minecraft.block.entity.BlockEntity;
 import net.minecraft.block.entity.TrappedChestBlockEntity;
 import net.minecraft.client.render.VertexConsumer;
-import net.minecraft.client.render.VertexConsumerProvider;
-import net.minecraft.client.render.VertexRendering;
 import net.minecraft.client.util.math.MatrixStack;
 import net.minecraft.util.math.Box;
-import net.minecraft.util.math.Vec3d;
 
 import java.util.concurrent.ConcurrentLinkedQueue;
 
@@ -22,74 +18,41 @@ public class MimicHighlight {
 
     private static final Box box = new Box(0.0625, 0, 0.0625, 0.9375, 0.875, 0.9375);
 
-    private static final ConcurrentLinkedQueue<TrappedChestBlockEntity> mimicList = new ConcurrentLinkedQueue<>();
+    private static final ConcurrentLinkedQueue<TrappedChestBlockEntity> mimics = new ConcurrentLinkedQueue<>();
 
     public static void init() {
 
         Events.ON_LOCATION_CHANGE.register(location -> {
-            mimicList.clear();
+            mimics.clear();
             return false;
         });
 
        Events.ON_BLOCK_ENTITY.register(blockEntity -> {
           if (!Location.inDungeon()) return false;
           if (blockEntity instanceof TrappedChestBlockEntity mimic) {
-              mimicList.add(mimic);
+              mimics.add(mimic);
           }
            return false;
        });
 
-       ClientTickEvents.END_CLIENT_TICK.register(client -> mimicList.removeIf(BlockEntity::isRemoved));
+       ClientTickEvents.END_CLIENT_TICK.register(client -> mimics.removeIf(BlockEntity::isRemoved));
 
-        WorldRenderEvents.AFTER_ENTITIES.register(MimicHighlight::renderFilled);
-        WorldRenderEvents.AFTER_ENTITIES.register(MimicHighlight::renderOutline);
+        RenderingEvents.FILLED_ENTITY.register(MimicHighlight::renderFilled);
+        RenderingEvents.OUTLINE_ENTITY.register(MimicHighlight::renderOutline);
     }
 
-    private static void renderFilled(WorldRenderContext context) {
-        if (!MobHighlight.highlightMimicChests) return;
-        if (!MobHighlight.renderFilled()) return;
-        Vec3d camera = context.worldState().cameraRenderState.pos;
-        MatrixStack matrices = context.matrices();
-        if (matrices == null) return;
-        matrices.push();
-        matrices.translate(-camera.x, -camera.y, -camera.z);
 
-        VertexConsumerProvider consumers = context.consumers();
-        if (consumers == null) return;
-        VertexConsumer filledConsumer = consumers.getBuffer(RenderLayers.FILLED_ENTITY_LAYER);
+    private static void renderFilled(WorldRenderContext context, MatrixStack matrixStack, VertexConsumer consumer) {
+        if (!MobHighlight.mobHighlight || !MobHighlight.renderFilled()) return;
 
         float[] rgba = RenderUtils.toFloats(MobHighlight.mimicFilledColor);
-        mimicList.forEach(blockEntity -> {
-            Box renderBox = box.offset(blockEntity.getPos());
-            VertexRendering.drawFilledBox(matrices, filledConsumer, renderBox.minX, renderBox.minY, renderBox.minZ, renderBox.maxX, renderBox.maxY, renderBox.maxZ, rgba[0], rgba[1], rgba[2], rgba[3]);
-        });
-
-        matrices.pop();
-
+        mimics.forEach(mimic -> RenderUtils.renderFilled(matrixStack, consumer, box.offset(mimic.getPos()), rgba));
     }
 
-    private static void renderOutline(WorldRenderContext context) {
-        if (!MobHighlight.highlightMimicChests) return;
-        if (!MobHighlight.renderOutline()) return;
-        Vec3d camera = context.worldState().cameraRenderState.pos;
-        MatrixStack matrices = context.matrices();
-        if (matrices == null) return;
-        matrices.push();
-        matrices.translate(-camera.x, -camera.y, -camera.z);
-
-        VertexConsumerProvider consumers = context.consumers();
-        if (consumers == null) return;
-        VertexConsumer outlineConsumer = consumers.getBuffer(RenderLayers.getOutline(MobHighlight.outlineWidth));
+    private static void renderOutline(WorldRenderContext context, MatrixStack matrixStack, VertexConsumer consumer) {
+        if (!MobHighlight.mobHighlight || !MobHighlight.renderOutline()) return;
 
-        MatrixStack.Entry entry = matrices.peek();
         float[] rgba = RenderUtils.toFloats(MobHighlight.mimicOutlineColor);
-
-        mimicList.forEach(blockEntity -> {
-            Box renderBox = box.offset(blockEntity.getPos());
-            VertexRendering.drawBox(entry, outlineConsumer, renderBox, rgba[0], rgba[1], rgba[2], rgba[3]);
-        });
-
-        matrices.pop();
-
+        mimics.forEach(mimic -> RenderUtils.renderOutline(matrixStack, consumer, box.offset(mimic.getPos()), rgba));
     }
 }
diff --git a/src/main/java/blade/addon/features/highlight/MobHighlight.java b/src/main/java/blade/addon/features/highlight/MobHighlight.java
index 3afe97e..25696a4 100644
--- a/src/main/java/blade/addon/features/highlight/MobHighlight.java
+++ b/src/main/java/blade/addon/features/highlight/MobHighlight.java
@@ -1,25 +1,20 @@
 package blade.addon.features.highlight;
 
 import blade.addon.utils.Location;
-import blade.addon.utils.Misc;
 import blade.addon.utils.data.EntityUtil;
 import blade.addon.utils.events.Events;
 import blade.addon.utils.interfaces.ArmourStandHolder;
-import blade.addon.utils.rendering.RenderLayers;
 import blade.addon.utils.rendering.RenderUtils;
+import blade.addon.utils.rendering.RenderingEvents;
 import config.practical.manager.ConfigValue;
 import net.fabricmc.fabric.api.client.event.lifecycle.v1.ClientTickEvents;
 import net.fabricmc.fabric.api.client.rendering.v1.world.WorldRenderContext;
-import net.fabricmc.fabric.api.client.rendering.v1.world.WorldRenderEvents;
 import net.minecraft.client.MinecraftClient;
 import net.minecraft.client.network.ClientPlayerEntity;
 import net.minecraft.client.render.VertexConsumer;
-import net.minecraft.client.render.VertexConsumerProvider;
-import net.minecraft.client.render.VertexRendering;
 import net.minecraft.client.util.math.MatrixStack;
 import net.minecraft.client.world.ClientWorld;
 import net.minecraft.entity.Entity;
-import net.minecraft.entity.EntityDimensions;
 import net.minecraft.entity.decoration.ArmorStandEntity;
 import net.minecraft.entity.effect.StatusEffects;
 import net.minecraft.entity.mob.EndermanEntity;
@@ -33,7 +28,6 @@ import net.minecraft.item.Items;
 import net.minecraft.text.Text;
 import net.minecraft.text.TextColor;
 import net.minecraft.util.math.Box;
-import net.minecraft.util.math.Vec3d;
 
 import java.util.ArrayList;
 import java.util.concurrent.ConcurrentLinkedQueue;
@@ -69,7 +63,7 @@ public class MobHighlight {
     private static final ConcurrentLinkedQueue<DataHolder> savedEntities = new ConcurrentLinkedQueue<>();
 
 
-    private static boolean dontRenderHighlight = false;
+    public static boolean dontRenderHighlight = false;
     @ConfigValue
     public static boolean mobHighlight = false;
     @ConfigValue
@@ -169,8 +163,8 @@ public class MobHighlight {
             dontRenderHighlight = player.hasStatusEffect(StatusEffects.BLINDNESS);
         });
 
-        WorldRenderEvents.AFTER_ENTITIES.register(MobHighlight::renderFilled);
-        WorldRenderEvents.AFTER_ENTITIES.register(MobHighlight::renderOutline);
+        RenderingEvents.FILLED_ENTITY.register(MobHighlight::renderFilled);
+        RenderingEvents.OUTLINE_ENTITY.register(MobHighlight::renderOutline);
     }
 
     public static void testArmourStand(ArmorStandEntity armorStand, MinecraftClient client) {
@@ -331,9 +325,8 @@ public class MobHighlight {
         return currentHighlight == HighlightType.BOTH || currentHighlight == HighlightType.OUTLINE;
     }
 
-    public static Box getBox(Entity entity, Vec3d pos) {
-        EntityDimensions dimension = entity.getDimensions(entity.getPose());
-        Box box = dimension.getBoxAt(pos);
+    public static Box getBox(Entity entity) {
+        Box box = EntityUtil.getBox(entity);
 
         //only shows the head
         if (entity instanceof EndermanEntity && entity.isInvisible() && MobHighlight.dontShowInvisibleMobs) {
@@ -350,77 +343,38 @@ public class MobHighlight {
         return box;
     }
 
-    private static void renderFilled(WorldRenderContext context) {
-        if (!mobHighlight || dontRenderHighlight) return;
-        if (!renderFilled()) return;
-        Vec3d camera = context.worldState().cameraRenderState.pos;
-        MatrixStack matrices = context.matrices();
-        if (matrices == null) return;
-        matrices.push();
-        matrices.translate(-camera.x, -camera.y, -camera.z);
+    private static void renderFilled(WorldRenderContext context, MatrixStack matrixStack, VertexConsumer consumer) {
+        if (!MobHighlight.mobHighlight || dontRenderHighlight || !MobHighlight.renderFilled()) return;
 
-        VertexConsumerProvider consumers = context.consumers();
-        if (consumers == null) return;
-        VertexConsumer filledConsumer = consumers.getBuffer(RenderLayers.FILLED_ENTITY_LAYER);
-
-        double tickProgress = MinecraftClient.getInstance().getRenderTickCounter().getTickProgress(false);
-
-        savedEntities.forEach(dataHolder -> {
-
-            Entity entity = dataHolder.entity;
-            MobType type = dataHolder.type;
+        for (DataHolder savedEntity : savedEntities) {
+            Entity entity = savedEntity.entity;
+            MobType type = savedEntity.type;
 
             if (entity.isInvisible() && MobHighlight.dontShowInvisibleMobs && entity instanceof PlayerEntity) return;
 
-            Vec3d pos = Misc.getPos(entity, tickProgress);
-
-            Box box = getBox(entity, pos);
-
-
+            Box box = getBox(entity);
             int filledColor = getFilledColor(type);
             float[] rgba = RenderUtils.toFloats(filledColor);
-            VertexRendering.drawFilledBox(matrices, filledConsumer, box.minX, box.minY, box.minZ, box.maxX, box.maxY, box.maxZ, rgba[0], rgba[1], rgba[2], rgba[3]);
-        });
 
+            RenderUtils.renderFilled(matrixStack, consumer, box, rgba);
 
-        matrices.pop();
-
+        }
     }
 
-    private static void renderOutline(WorldRenderContext context) {
-        if (!mobHighlight || dontRenderHighlight) return;
-        if (!renderOutline()) return;
-        Vec3d camera = context.worldState().cameraRenderState.pos;
-        MatrixStack matrices = context.matrices();
-        if (matrices == null) return;
-        matrices.push();
-        matrices.translate(-camera.x, -camera.y, -camera.z);
+    private static void renderOutline(WorldRenderContext context, MatrixStack matrixStack, VertexConsumer consumer) {
+        if (!MobHighlight.mobHighlight || dontRenderHighlight || !MobHighlight.renderOutline()) return;
 
-        VertexConsumerProvider consumers = context.consumers();
-        if (consumers == null) return;
-        VertexConsumer outlineConsumer = consumers.getBuffer(RenderLayers.getOutline(outlineWidth));
-
-        double tickProgress = MinecraftClient.getInstance().getRenderTickCounter().getTickProgress(false);
-
-        MatrixStack.Entry entry = matrices.peek();
-
-        savedEntities.forEach(dataHolder -> {
-
-            Entity entity = dataHolder.entity;
-            MobType type = dataHolder.type;
+        for (DataHolder savedEntity : savedEntities) {
+            Entity entity = savedEntity.entity;
+            MobType type = savedEntity.type;
 
             if (entity.isInvisible() && MobHighlight.dontShowInvisibleMobs && entity instanceof PlayerEntity) return;
 
-            Vec3d pos = Misc.getPos(entity, tickProgress);
-            Box box = getBox(entity, pos);
-
+            Box box = getBox(entity);
             int outlineColor = getOutlineColor(type);
             float[] rgba = RenderUtils.toFloats(outlineColor);
-            VertexRendering.drawBox(entry, outlineConsumer, box, rgba[0], rgba[1], rgba[2], rgba[3]);
-        });
-
-
-        matrices.pop();
 
+            RenderUtils.renderOutline(matrixStack, consumer, box, rgba);
+        }
     }
 }
diff --git a/src/main/java/blade/addon/features/highlight/SheepHighlight.java b/src/main/java/blade/addon/features/highlight/SheepHighlight.java
index 56bd5ea..0e3121f 100644
--- a/src/main/java/blade/addon/features/highlight/SheepHighlight.java
+++ b/src/main/java/blade/addon/features/highlight/SheepHighlight.java
@@ -1,110 +1,55 @@
 package blade.addon.features.highlight;
 
 import blade.addon.utils.Location;
-import blade.addon.utils.Misc;
+import blade.addon.utils.data.EntityUtil;
 import blade.addon.utils.events.Events;
-import blade.addon.utils.rendering.RenderLayers;
 import blade.addon.utils.rendering.RenderUtils;
+import blade.addon.utils.rendering.RenderingEvents;
 import net.fabricmc.fabric.api.client.event.lifecycle.v1.ClientTickEvents;
 import net.fabricmc.fabric.api.client.rendering.v1.world.WorldRenderContext;
-import net.fabricmc.fabric.api.client.rendering.v1.world.WorldRenderEvents;
-import net.minecraft.client.MinecraftClient;
 import net.minecraft.client.render.VertexConsumer;
-import net.minecraft.client.render.VertexConsumerProvider;
-import net.minecraft.client.render.VertexRendering;
 import net.minecraft.client.util.math.MatrixStack;
-import net.minecraft.entity.EntityDimensions;
 import net.minecraft.entity.passive.SheepEntity;
-import net.minecraft.util.math.Box;
-import net.minecraft.util.math.Vec3d;
 
 import java.util.concurrent.ConcurrentLinkedQueue;
 
 public class SheepHighlight {
 
-    private static final ConcurrentLinkedQueue<SheepEntity> sheepList = new ConcurrentLinkedQueue<>();
+    private static final ConcurrentLinkedQueue<SheepEntity> sheeps = new ConcurrentLinkedQueue<>();
 
     public static void init() {
 
         Events.ON_ENTITY_SPAWNED.register((entity, world) -> {
             if (!Location.inDungeon()) return false;
             if (entity instanceof SheepEntity sheep) {
-                sheepList.add(sheep);
+                sheeps.add(sheep);
             }
             return false;
         });
 
         Events.ON_LOCATION_CHANGE.register(location -> {
-            sheepList.clear();
+            sheeps.clear();
             return false;
         });
 
-        ClientTickEvents.END_CLIENT_TICK.register(client -> sheepList.removeIf(sheep -> sheep.isRemoved() || sheep.isDead()));
+        ClientTickEvents.END_CLIENT_TICK.register(client -> sheeps.removeIf(sheep -> sheep.isRemoved() || sheep.isDead()));
 
-        WorldRenderEvents.AFTER_ENTITIES.register(SheepHighlight::renderFilled);
-        WorldRenderEvents.AFTER_ENTITIES.register(SheepHighlight::renderOutline);
+        RenderingEvents.FILLED_ENTITY.register(SheepHighlight::renderFilled);
+        RenderingEvents.OUTLINE_ENTITY.register(SheepHighlight::renderOutline);
     }
 
-    private static void renderFilled(WorldRenderContext context) {
-        if (!MobHighlight.highlightSheep) return;
-        if (!MobHighlight.renderFilled()) return;
-        Vec3d camera = context.worldState().cameraRenderState.pos;
-        MatrixStack matrices = context.matrices();
-        if (matrices == null) return;
-        matrices.push();
-        matrices.translate(-camera.x, -camera.y, -camera.z);
-
-        VertexConsumerProvider consumers = context.consumers();
-        if (consumers == null) return;
-        VertexConsumer filledConsumer = consumers.getBuffer(RenderLayers.FILLED_ENTITY_LAYER);
-
-        double tickProgress = MinecraftClient.getInstance().getRenderTickCounter().getTickProgress(false);
+    private static void renderFilled(WorldRenderContext context, MatrixStack matrixStack, VertexConsumer consumer) {
+        if (!MobHighlight.highlightSheep || !MobHighlight.renderFilled()) return;
 
         float[] rgba = RenderUtils.toFloats(MobHighlight.sheepFilledColor);
-        sheepList.forEach(entity -> {
-
-            Vec3d pos = Misc.getPos(entity, tickProgress);
-
-            EntityDimensions dimension = entity.getDimensions(entity.getPose());
-            Box box = dimension.getBoxAt(pos);
-
-            VertexRendering.drawFilledBox(matrices, filledConsumer, box.minX, box.minY, box.minZ, box.maxX, box.maxY, box.maxZ, rgba[0], rgba[1], rgba[2], rgba[3]);
-        });
-
-        matrices.pop();
-
+        sheeps.forEach(entity -> RenderUtils.renderFilled(matrixStack, consumer, EntityUtil.getBox(entity), rgba));
     }
 
-    private static void renderOutline(WorldRenderContext context) {
-        if (!MobHighlight.highlightSheep) return;
-        if (!MobHighlight.renderOutline()) return;
-        Vec3d camera = context.worldState().cameraRenderState.pos;
-        MatrixStack matrices = context.matrices();
-        if (matrices == null) return;
-        matrices.push();
-        matrices.translate(-camera.x, -camera.y, -camera.z);
-
-        VertexConsumerProvider consumers = context.consumers();
-        if (consumers == null) return;
-        VertexConsumer outlineConsumer = consumers.getBuffer(RenderLayers.getOutline(MobHighlight.outlineWidth));
-
-        double tickProgress = MinecraftClient.getInstance().getRenderTickCounter().getTickProgress(false);
-
-        MatrixStack.Entry entry = matrices.peek();
-        float[] rgba = RenderUtils.toFloats(MobHighlight.sheepOutlineColor);
-
-        sheepList.forEach(entity -> {
-
-            Vec3d pos = Misc.getPos(entity, tickProgress);
-
-            EntityDimensions dimension = entity.getDimensions(entity.getPose());
-            Box box = dimension.getBoxAt(pos);
-
-            VertexRendering.drawBox(entry, outlineConsumer, box, rgba[0], rgba[1], rgba[2], rgba[3]);
-        });
-
-        matrices.pop();
+    private static void renderOutline(WorldRenderContext context, MatrixStack matrixStack, VertexConsumer consumer) {
+        if (!MobHighlight.highlightSheep || !MobHighlight.renderOutline()) return;
 
+        float[] rgba = RenderUtils.toFloats(MobHighlight.sheepFilledColor);
+        sheeps.forEach(entity -> RenderUtils.renderOutline(matrixStack, consumer, EntityUtil.getBox(entity), rgba));
     }
 
 }
diff --git a/src/main/java/blade/addon/features/highlight/WitherHighlight.java b/src/main/java/blade/addon/features/highlight/WitherHighlight.java
index c8f0dd6..b90f1b1 100644
--- a/src/main/java/blade/addon/features/highlight/WitherHighlight.java
+++ b/src/main/java/blade/addon/features/highlight/WitherHighlight.java
@@ -1,23 +1,17 @@
 package blade.addon.features.highlight;
 
 import blade.addon.utils.Location;
-import blade.addon.utils.Misc;
+import blade.addon.utils.data.EntityUtil;
 import blade.addon.utils.dungeon.Phase;
 import blade.addon.utils.events.Events;
-import blade.addon.utils.rendering.RenderLayers;
 import blade.addon.utils.rendering.RenderUtils;
+import blade.addon.utils.rendering.RenderingEvents;
 import net.fabricmc.fabric.api.client.event.lifecycle.v1.ClientTickEvents;
 import net.fabricmc.fabric.api.client.rendering.v1.world.WorldRenderContext;
-import net.fabricmc.fabric.api.client.rendering.v1.world.WorldRenderEvents;
-import net.minecraft.client.MinecraftClient;
 import net.minecraft.client.render.VertexConsumer;
-import net.minecraft.client.render.VertexConsumerProvider;
-import net.minecraft.client.render.VertexRendering;
 import net.minecraft.client.util.math.MatrixStack;
-import net.minecraft.entity.EntityDimensions;
 import net.minecraft.entity.boss.WitherEntity;
 import net.minecraft.util.math.Box;
-import net.minecraft.util.math.Vec3d;
 
 import java.util.concurrent.ConcurrentLinkedQueue;
 
@@ -50,70 +44,26 @@ public class WitherHighlight {
 
         ClientTickEvents.END_CLIENT_TICK.register(client -> withers.removeIf(wither -> wither.isRemoved() || wither.isDead()));
 
-        WorldRenderEvents.AFTER_ENTITIES.register(WitherHighlight::renderFilled);
-        WorldRenderEvents.AFTER_ENTITIES.register(WitherHighlight::renderOutline);
+        RenderingEvents.FILLED_ENTITY.register(WitherHighlight::renderFilled);
+        RenderingEvents.OUTLINE_ENTITY.register(WitherHighlight::renderOutline);
     }
 
-    private static void renderFilled(WorldRenderContext context) {
-        if (!MobHighlight.mobHighlight) return;
-        if (!MobHighlight.renderFilled()) return;
-        Vec3d camera = context.worldState().cameraRenderState.pos;
-        MatrixStack matrices = context.matrices();
-        if (matrices == null) return;
-        matrices.push();
-        matrices.translate(-camera.x, -camera.y, -camera.z);
-
-        VertexConsumerProvider consumers = context.consumers();
-        if (consumers == null) return;
-        VertexConsumer filledConsumer = consumers.getBuffer(RenderLayers.FILLED_ENTITY_LAYER);
-
-        double tickProgress = MinecraftClient.getInstance().getRenderTickCounter().getTickProgress(false);
-
-        float[] rgba = RenderUtils.toFloats(MobHighlight.witherFilledColor);
-        withers.forEach(entity -> {
-
-            Vec3d pos = Misc.getPos(entity, tickProgress);
-
-            EntityDimensions dimension = entity.getDimensions(entity.getPose());
-            Box box = dimension.getBoxAt(pos).expand(MobHighlight.witherExtraWidth, 0, MobHighlight.witherExtraWidth);
-
-            VertexRendering.drawFilledBox(matrices, filledConsumer, box.minX, box.minY, box.minZ, box.maxX, box.maxY, box.maxZ, rgba[0], rgba[1], rgba[2], rgba[3]);
-        });
-
-        matrices.pop();
-
+    private static Box getBox(WitherEntity wither) {
+        return EntityUtil.getBox(wither).expand(MobHighlight.witherExtraWidth, 0, MobHighlight.witherExtraWidth);
     }
 
-    private static void renderOutline(WorldRenderContext context) {
-        if (!MobHighlight.mobHighlight) return;
-        if (!MobHighlight.renderOutline()) return;
-        Vec3d camera = context.worldState().cameraRenderState.pos;
-        MatrixStack matrices = context.matrices();
-        if (matrices == null) return;
-        matrices.push();
-        matrices.translate(-camera.x, -camera.y, -camera.z);
+    private static void renderFilled(WorldRenderContext context, MatrixStack matrixStack, VertexConsumer consumer) {
+        if (!MobHighlight.mobHighlight || !MobHighlight.renderFilled() || MobHighlight.dontRenderHighlight) return;
 
-        VertexConsumerProvider consumers = context.consumers();
-        if (consumers == null) return;
-        VertexConsumer outlineConsumer = consumers.getBuffer(RenderLayers.getOutline(MobHighlight.outlineWidth));
-
-        double tickProgress = MinecraftClient.getInstance().getRenderTickCounter().getTickProgress(false);
-
-        MatrixStack.Entry entry = matrices.peek();
-        float[] rgba = RenderUtils.toFloats(MobHighlight.witherOutlineColor);
-
-        withers.forEach(entity -> {
-
-            Vec3d pos = Misc.getPos(entity, tickProgress);
-
-            EntityDimensions dimension = entity.getDimensions(entity.getPose());
-            Box box = dimension.getBoxAt(pos).expand(MobHighlight.witherExtraWidth, 0, MobHighlight.witherExtraWidth);
-
-            VertexRendering.drawBox(entry, outlineConsumer, box, rgba[0], rgba[1], rgba[2], rgba[3]);
-        });
+        float[] rgba = RenderUtils.toFloats(MobHighlight.batFilledColor);
+        withers.forEach(entity -> RenderUtils.renderFilled(matrixStack, consumer, getBox(entity), rgba));
+    }
 
-        matrices.pop();
+    private static void renderOutline(WorldRenderContext context, MatrixStack matrixStack, VertexConsumer consumer) {
+        if (!MobHighlight.mobHighlight || !MobHighlight.renderOutline() || MobHighlight.dontRenderHighlight) return;
 
+        float[] rgba = RenderUtils.toFloats(MobHighlight.batOutlineColor);
+        withers.forEach(entity -> RenderUtils.renderOutline(matrixStack, consumer, getBox(entity), rgba));
     }
 
 }
diff --git a/src/main/java/blade/addon/features/item/HeldItemToolTip.java b/src/main/java/blade/addon/features/item/HeldItemToolTip.java
new file mode 100644
index 0000000..14caeea
--- /dev/null
+++ b/src/main/java/blade/addon/features/item/HeldItemToolTip.java
@@ -0,0 +1,36 @@
+package blade.addon.features.item;
+
+import blade.addon.utils.config.values.ExtraOptions;
+import blade.addon.utils.rendering.RenderUtils;
+import config.practical.hud.HUDComponent;
+import net.minecraft.client.gui.DrawContext;
+import net.minecraft.text.Text;
+import net.minecraft.util.math.ColorHelper;
+
+public class HeldItemToolTip {
+
+    private static Text tooltip = Text.literal("Some toolTip");
+    private static int color = 0xffffffff;
+    private static int heldItemFade = 0;
+
+    public static void setText(Text newToolTip) {
+        tooltip = newToolTip;
+    }
+
+    public static void setColor(int newColor) {
+        color = ColorHelper.withAlpha(newColor, -1);
+    }
+
+    public static void setFade(int fade) {
+        heldItemFade = fade;
+    }
+
+    public static boolean display() {
+        return ExtraOptions.moveToolTip && heldItemFade > 0;
+    }
+
+    public static void render(HUDComponent component, DrawContext context) {
+        RenderUtils.drawCenteredText(context, component, tooltip, color);
+    }
+
+}
diff --git a/src/main/java/blade/addon/features/notifications/Notification.java b/src/main/java/blade/addon/features/notifications/Notification.java
index 47c5f73..afe42dd 100644
--- a/src/main/java/blade/addon/features/notifications/Notification.java
+++ b/src/main/java/blade/addon/features/notifications/Notification.java
@@ -11,7 +11,7 @@ import java.util.regex.PatternSyntaxException;
 public class Notification {
 
     private String matchString, notificationString, command;
-    private boolean useRegex, sendCommand;
+    private boolean useRegex, sendCommand, enabled;
     private int ticks;
     private final SoundData sound;
 
@@ -21,16 +21,18 @@ public class Notification {
         this.command = "";
         this.useRegex = false;
         this.sendCommand = false;
+        this.enabled = true;
         this.ticks = 20;
         this.sound = new SoundData(SoundEvents.BLOCK_NOTE_BLOCK_PLING.value(), 1, 1);
     }
 
-    public Notification(String matchString, String notificationString, String command, boolean useRegex, boolean sendCommand, int ticks, SoundData sound) {
+    public Notification(String matchString, String notificationString, String command, boolean useRegex, boolean sendCommand, boolean enabled, int ticks, SoundData sound) {
         this.matchString = matchString;
         this.notificationString = notificationString;
         this.command = command;
         this.useRegex = useRegex;
         this.sendCommand = sendCommand;
+        this.enabled = enabled;
         this.ticks = ticks;
         this.sound = sound;
     }
@@ -63,6 +65,9 @@ public class Notification {
         return useRegex;
     }
 
+    public boolean isEnabled() {
+        return enabled;
+    }
 
     public void setMatchString(String matchString) {
         this.matchString = matchString;
@@ -88,7 +93,12 @@ public class Notification {
         this.useRegex = useRegex;
     }
 
+    public void setEnabled(boolean enabled) {
+        this.enabled = enabled;
+    }
+
     public void testMessage(String message) {
+        if (!enabled) return;
         if (useRegex) {
             try {
                 if (!message.matches(matchString)) return;
@@ -113,6 +123,7 @@ public class Notification {
                 ", command='" + command + '\'' +
                 ", useRegex=" + useRegex +
                 ", sendCommand=" + sendCommand +
+                ", enabled=" + enabled +
                 ", ticks=" + ticks +
                 ", sound=" + sound +
                 '}';
diff --git a/src/main/java/blade/addon/features/notifications/NotificationEditScreen.java b/src/main/java/blade/addon/features/notifications/NotificationEditScreen.java
index 94a34e3..e4d796e 100644
--- a/src/main/java/blade/addon/features/notifications/NotificationEditScreen.java
+++ b/src/main/java/blade/addon/features/notifications/NotificationEditScreen.java
@@ -30,6 +30,7 @@ public class NotificationEditScreen extends Screen {
 
     private void initScrollWidgets() {
         scroll.children().clear();
+        scroll.add(new ConfigBool(Text.literal("Enabled"), notification::isEnabled, notification::setEnabled));
         scroll.add(new ConfigString(Text.literal("Trigger on message"), notification::getMatchString, notification::setMatchString, false));
         scroll.add(new ConfigString(Text.literal("Notification message"), notification::getNotificationString, notification::setNotificationString));
         scroll.add(new ConfigBool(Text.literal("Use regex"), notification::useRegex, notification::setUseRegex));
diff --git a/src/main/java/blade/addon/features/notifications/NotificationEntry.java b/src/main/java/blade/addon/features/notifications/NotificationEntry.java
index 598b369..85c8149 100644
--- a/src/main/java/blade/addon/features/notifications/NotificationEntry.java
+++ b/src/main/java/blade/addon/features/notifications/NotificationEntry.java
@@ -4,19 +4,25 @@ import config.practical.utilities.Constants;
 import config.practical.utilities.DrawHelper;
 import net.minecraft.client.MinecraftClient;
 import net.minecraft.client.font.TextRenderer;
+import net.minecraft.client.gl.RenderPipelines;
 import net.minecraft.client.gui.Click;
 import net.minecraft.client.gui.DrawContext;
 import net.minecraft.client.gui.screen.narration.NarrationMessageBuilder;
 import net.minecraft.client.gui.widget.ClickableWidget;
 import net.minecraft.text.Text;
+import net.minecraft.util.Identifier;
+import net.minecraft.util.Pair;
 
 class NotificationEntry extends ClickableWidget {
 
+    private static final Identifier CROSS = Identifier.of(blade.addon.utils.Constants.NAMESPACE, "cross");
+
     private static final int PADDING = 5;
-    private static final int TEXT_SPACE = 100;
-    private static final int MAX_TEXT_WIDTH = 75;
-    private static final int BUTTON_WIDTH = 60;
+    private static final int TEXT_SPACE = 125;
+    private static final int MAX_TEXT_WIDTH = 100;
 
+    private static final int SPRITE_SIZE = 16;
+    private static final int SPRITE_WIDTH_AREA = SPRITE_SIZE + 10;
 
     private final NotificationList parent ;
     private final Notification notification;
@@ -33,7 +39,7 @@ class NotificationEntry extends ClickableWidget {
         int y = getY();
         int width = getWidth();
         int height = getHeight();
-        DrawHelper.drawBackground(context, x, y, width, height);
+        DrawHelper.drawBackground(context, x, y, width - SPRITE_WIDTH_AREA, height);
 
         MinecraftClient mc = MinecraftClient.getInstance();
         TextRenderer textRenderer = mc.textRenderer;
@@ -41,11 +47,8 @@ class NotificationEntry extends ClickableWidget {
         drawText(context, textRenderer, notification.getMatchString(), x + PADDING, y);
         drawText(context, textRenderer, notification.getNotificationString(), x + PADDING * 2 + TEXT_SPACE, y);
 
-        int buttonWidth = 60;
-        String remove = "Remove";
-        textRenderer.trimToWidth(remove, TEXT_SPACE - 2);
-        DrawHelper.drawBackground(context, x + width - buttonWidth - PADDING, y + 4, buttonWidth, height - 8);
-        context.drawText(textRenderer, remove, x + width - buttonWidth, y + (30 - Constants.TEXT_HEIGHT) / 2, 0xffffffff, true);
+        Pair<Integer, Integer> pos = getRemovePos();
+        context.drawGuiTexture(RenderPipelines.GUI_TEXTURED, CROSS, pos.getLeft(), pos.getRight(), SPRITE_SIZE, SPRITE_SIZE, 0xffffffff);
     }
 
     private void drawText(DrawContext context, TextRenderer textRenderer, String string, int x, int y) {
@@ -64,16 +67,28 @@ class NotificationEntry extends ClickableWidget {
         context.disableScissor();
     }
 
+    private Pair<Integer, Integer> getRemovePos() {
+        return new Pair<>(getX() + width - SPRITE_SIZE - 5, getY() + (height - SPRITE_SIZE) / 2);
+    }
+
+    private boolean inRemovalBounds(double x, double y) {
+        Pair<Integer, Integer> pos = getRemovePos();
+        return x >= pos.getLeft() && x <=  pos.getLeft() + SPRITE_SIZE && y >= pos.getRight() && y <= pos.getRight() + SPRITE_SIZE;
+    }
+
     @Override
     public void onClick(Click click, boolean doubled) {
         double x = click.x();
         double y = click.y();
 
-        if (x >= getX() + width - BUTTON_WIDTH - PADDING && x <= getX() + width + BUTTON_WIDTH && y >= getY() + 4 && y <= getY() + height - 4) {
+        if (inRemovalBounds(x, y)) {
             parent.removeNotification(notification);
             return;
         }
 
+        //make sure you cant click around the button accidentally
+        if (width + getX() - SPRITE_WIDTH_AREA < x) return;
+
         MinecraftClient.getInstance().setScreen(new NotificationEditScreen(notification));
     }
 
diff --git a/src/main/java/blade/addon/features/notifications/Notifications.java b/src/main/java/blade/addon/features/notifications/Notifications.java
index 8611e83..fad8b49 100644
--- a/src/main/java/blade/addon/features/notifications/Notifications.java
+++ b/src/main/java/blade/addon/features/notifications/Notifications.java
@@ -36,7 +36,6 @@ public class Notifications {
     public static void init() {
         notifications = new CopyOnWriteArrayList<>();
         load();
-        System.out.println("Noti: " + notifications);
         Events.ON_GAME_MESSAGE.register(text -> {
             String message = text.getString();
             for (Notification notification : notifications) {
@@ -46,9 +45,7 @@ public class Notifications {
             return false;
         });
 
-        ClientTickEvents.END_CLIENT_TICK.register(event -> {
-            tick = Math.max(tick - 1, 0);
-        });
+        ClientTickEvents.END_CLIENT_TICK.register(event -> tick = Math.max(tick - 1, 0));
     }
 
     public static void addNotification(Notification notification) {
@@ -129,6 +126,9 @@ public class Notifications {
                 if (!notification.has("sendCommand")) continue;
                 boolean sendCommand = notification.get("sendCommand").getAsBoolean();
 
+                if (!notification.has("enabled")) continue;
+                boolean enabled = notification.get("enabled").getAsBoolean();
+
                 if (!notification.has("ticks")) continue;
                 int ticks = notification.get("ticks").getAsInt();
 
@@ -136,7 +136,7 @@ public class Notifications {
                 SoundData sound = loadSoundData(notification.get("sound"));
                 if  (sound == null) continue;
 
-                notifications.add(new Notification(matchString, notificationString, command, useRegex, sendCommand, ticks, sound));
+                notifications.add(new Notification(matchString, notificationString, command, useRegex, sendCommand, enabled, ticks, sound));
             } catch (NumberFormatException e) {
                 System.out.println("msg: " + e.getMessage());
             }
diff --git a/src/main/java/blade/addon/features/other/PracticeSS.java b/src/main/java/blade/addon/features/other/PracticeSS.java
index 85ee28d..de46bf1 100644
--- a/src/main/java/blade/addon/features/other/PracticeSS.java
+++ b/src/main/java/blade/addon/features/other/PracticeSS.java
@@ -7,6 +7,7 @@ import blade.addon.utils.config.values.ExtraOptions;
 import blade.addon.utils.events.Events;
 import blade.addon.utils.rendering.RenderLayers;
 import blade.addon.utils.rendering.RenderUtils;
+import blade.addon.utils.rendering.RenderingEvents;
 import blade.addon.utils.times.PersonalBests;
 import net.fabricmc.fabric.api.client.event.lifecycle.v1.ClientTickEvents;
 import net.fabricmc.fabric.api.client.rendering.v1.world.WorldRenderContext;
@@ -116,7 +117,7 @@ public class PracticeSS {
             }
         });
 
-        WorldRenderEvents.BEFORE_DEBUG_RENDER.register(PracticeSS::render);
+        RenderingEvents.FILLED_BLOCK.register(PracticeSS::render);
         Events.ON_LOCAT
…… diff 过长已截断 ……
- hopefully improved highlighting - moved around and made config file a bit easier to navigate - added selectable sound for ssGabriel Ellnebrand · 2026-01-13 · 4cbfae9
查看变更
src/main/java/blade/addon/Bladeaddons.java                           |   4 ++
 src/main/java/blade/addon/features/dungeon/f7/RelicTimer.java        |  21 ++++----
 src/main/java/blade/addon/features/highlight/BatHighlight.java       | 116 ++++++++++++++++++++++++++++++++++++++++++++
 src/main/java/blade/addon/features/highlight/MobHighlight.java       | 220 +++++++++++++++++++++++++++++------------------------------------------------------
 src/main/java/blade/addon/features/highlight/SheepHighlight.java     |   4 +-
 src/main/java/blade/addon/features/highlight/WitherHighlight.java    | 119 +++++++++++++++++++++++++++++++++++++++++++++
 src/main/java/blade/addon/features/other/CompactHoppity.java         |   4 +-
 src/main/java/blade/addon/features/other/PracticeSS.java             |   9 ++++
 src/main/java/blade/addon/mixin/ArmorStandEntityMixin.java           |  23 +++++++++
 src/main/java/blade/addon/mixin/DrawContextMixin.java                |  17 +++++++
 src/main/java/blade/addon/mixin/EntityMixin.java                     |   8 +--
 src/main/java/blade/addon/mixin/EntityRenderManagerMixin.java        |   4 +-
 src/main/java/blade/addon/mixin/EntityRendererMixin.java             |   9 +++-
 src/main/java/blade/addon/mixin/HandledScreenMixin.java              |   7 +--
 src/main/java/blade/addon/mixin/InGameHudMixin.java                  |  10 ++--
 src/main/java/blade/addon/mixin/StatusEffectsDisplayMixin.java       |   6 +--
 src/main/java/blade/addon/mixin/StuckArrowsFeatureRendererMixin.java |   4 +-
 src/main/java/blade/addon/utils/config/Config.java                   |  79 ++++++++++++++++++++----------
 src/main/java/blade/addon/utils/config/values/ExtraOptions.java      |  30 ++----------
 src/main/java/blade/addon/utils/config/values/Visual.java            |  40 +++++++++++++++
 src/main/java/blade/addon/utils/events/Events.java                   |   1 -
 src/main/java/blade/addon/utils/interfaces/ArmourStandHolder.java    |  10 ++++
 src/main/resources/blade-addons.mixins.json                          |   2 +
 23 files changed, 514 insertions(+), 233 deletions(-)
diff --git a/src/main/java/blade/addon/Bladeaddons.java b/src/main/java/blade/addon/Bladeaddons.java
index 0475cec..ef80bf6 100644
--- a/src/main/java/blade/addon/Bladeaddons.java
+++ b/src/main/java/blade/addon/Bladeaddons.java
@@ -26,9 +26,11 @@ import blade.addon.features.dungeon.f7.terms.LeapNotification;
 import blade.addon.features.dungeon.f7.terms.MelodyWarning;
 import blade.addon.features.dungeon.f7.terms.Pre4Notifier;
 import blade.addon.features.dungeon.f7.terms.TermStartTimer;
+import blade.addon.features.highlight.BatHighlight;
 import blade.addon.features.highlight.MimicHighlight;
 import blade.addon.features.highlight.MobHighlight;
 import blade.addon.features.highlight.SheepHighlight;
+import blade.addon.features.highlight.WitherHighlight;
 import blade.addon.features.item.DropAnimation;
 import blade.addon.features.notifications.Notifications;
 import blade.addon.features.other.CompactHoppity;
@@ -108,5 +110,7 @@ public class Bladeaddons implements ModInitializer {
         MimicHighlight.init();
         SheepHighlight.init();
         Notifications.init();
+        BatHighlight.init();
+        WitherHighlight.init();
     }
 }
\ No newline at end of file
diff --git a/src/main/java/blade/addon/features/dungeon/f7/RelicTimer.java b/src/main/java/blade/addon/features/dungeon/f7/RelicTimer.java
index b6f5948..6b2fa42 100644
--- a/src/main/java/blade/addon/features/dungeon/f7/RelicTimer.java
+++ b/src/main/java/blade/addon/features/dungeon/f7/RelicTimer.java
@@ -14,7 +14,6 @@ import blade.addon.utils.rendering.RenderUtils;
 import blade.addon.utils.times.PersonalBests;
 import config.practical.hud.HUDComponent;
 import net.fabricmc.fabric.api.client.event.lifecycle.v1.ClientTickEvents;
-import net.fabricmc.fabric.api.client.message.v1.ClientReceiveMessageEvents;
 import net.fabricmc.fabric.api.client.rendering.v1.world.WorldRenderEvents;
 import net.minecraft.client.MinecraftClient;
 import net.minecraft.client.gui.DrawContext;
@@ -57,7 +56,6 @@ public class RelicTimer {
         }
     }
 
-    private static boolean ignoreChecks = false;
 
     private static final int GREEN_COLOR = 0xff00ff00;
     private static final int RED_COLOR = 0xffff0000;
@@ -68,7 +66,9 @@ public class RelicTimer {
     private static Relic pickedupRelic = null;
 
     private static int tick = Floor7.relicSpawnTicks;
+    private static boolean sentRelicTimes = false;
 
+    private static boolean ignoreChecks = false;
     //only here to test the relic progress bar
     private static boolean forceGUI = false;
 
@@ -101,9 +101,8 @@ public class RelicTimer {
             return false;
         });
 
-        ClientReceiveMessageEvents.GAME.register((message, overlay) -> {
-            if (!Location.inDungeon() || !Phase.inP5()) return;
-            Debug.sendDebugMessage(Text.literal("Debug: ").append(message));
+        Events.ON_GAME_MESSAGE.register(message -> {
+            if (!Location.inDungeon() || !Phase.inP5()) return false;
             String str = message.getString().replaceAll("§.", "");
             Matcher matcher = PATTERN.matcher(str);
             if (matcher.find()) {
@@ -111,24 +110,24 @@ public class RelicTimer {
                 String relicString = matcher.group(2);
 
                 ClientPlayerEntity player = MinecraftClient.getInstance().player;
-                if (player == null) return;
+                if (player == null) return false;
                 if (name.equals(player.getName().getString())) {
                     pickedupRelic = Relic.valueOf(relicString.toUpperCase());
                     Debug.sendDebugMessage(Text.literal("Picked up relic " + relicString));
                 }
             }
-        });
 
+            return false;
+        });
 
         ClientTickEvents.END_WORLD_TICK.register(world -> {
-            if (!Location.inDungeon() || !Phase.inP5() || !Floor7.showAllRelicTimes) return;
+            if (!Location.inDungeon() || !Phase.inP5() || !Floor7.showAllRelicTimes ||sentRelicTimes) return;
 
             Iterable<Entity> entities = world.getEntities();
-            if (allRelicsPlaced()) return;
 
             for (Entity entity : entities) {
                 if (entity instanceof ArmorStandEntity armorStand) {
-                    if (!EntityUtil.isWearing(armorStand, EquipmentSlot.HEAD, "Relic")) return;
+                    if (!EntityUtil.isWearing(armorStand, EquipmentSlot.HEAD, "Relic")) continue;
 
                     double ax = armorStand.getX();
                     double az = armorStand.getZ();
@@ -146,6 +145,7 @@ public class RelicTimer {
                 for (Relic relic : Relic.values()) {
                     long time = relic.placedTime - phaseStartTime;
                     Misc.addChatMessage(Text.literal(relic.name + " &aRelic placed in &e" + Constants.DECIMAL_FORMAT.format(time) + "s&a."));
+                    sentRelicTimes = true;
                 }
             }
         });
@@ -225,6 +225,7 @@ public class RelicTimer {
         for (Relic relic : Relic.values()) {
             relic.placedTime = 0;
         }
+        sentRelicTimes = false;
     }
 
     private static boolean allRelicsPlaced() {
diff --git a/src/main/java/blade/addon/features/highlight/BatHighlight.java b/src/main/java/blade/addon/features/highlight/BatHighlight.java
new file mode 100644
index 0000000..80e1d89
--- /dev/null
+++ b/src/main/java/blade/addon/features/highlight/BatHighlight.java
@@ -0,0 +1,116 @@
+package blade.addon.features.highlight;
+
+import blade.addon.utils.Location;
+import blade.addon.utils.Misc;
+import blade.addon.utils.events.Events;
+import blade.addon.utils.rendering.RenderLayers;
+import blade.addon.utils.rendering.RenderUtils;
+import net.fabricmc.fabric.api.client.event.lifecycle.v1.ClientTickEvents;
+import net.fabricmc.fabric.api.client.rendering.v1.world.WorldRenderContext;
+import net.fabricmc.fabric.api.client.rendering.v1.world.WorldRenderEvents;
+import net.minecraft.client.MinecraftClient;
+import net.minecraft.client.render.VertexConsumer;
+import net.minecraft.client.render.VertexConsumerProvider;
+import net.minecraft.client.render.VertexRendering;
+import net.minecraft.client.util.math.MatrixStack;
+import net.minecraft.entity.EntityDimensions;
+import net.minecraft.entity.passive.BatEntity;
+import net.minecraft.util.math.Box;
+import net.minecraft.util.math.Vec3d;
+
+import java.util.concurrent.ConcurrentLinkedQueue;
+
+public class BatHighlight {
+
+    private static final float[] BAT_HEALTHS = {100.0f, 200.0f, 400.0f, 800.0f};
+
+    private static final ConcurrentLinkedQueue<BatEntity> bats = new ConcurrentLinkedQueue<>();
+
+    public static void init() {
+
+        Events.ON_ENTITY_TRACKED.register((entity, world) -> {
+            if (!Location.inDungeon() || !MobHighlight.mobHighlight) return false;
+            if (entity instanceof BatEntity bat) {
+                for (float health : BAT_HEALTHS) {
+                    if (health == bat.getHealth()) {
+                        bats.add(bat);
+                    }
+                }
+            }
+            return false;
+        });
+
+        Events.ON_LOCATION_CHANGE.register(location -> {
+            bats.clear();
+            return false;
+        });
+
+        ClientTickEvents.END_CLIENT_TICK.register(client -> bats.removeIf(bat -> bat.isRemoved() ||bat.isDead()));
+
+        WorldRenderEvents.AFTER_ENTITIES.register(BatHighlight::renderFilled);
+        WorldRenderEvents.AFTER_ENTITIES.register(BatHighlight::renderOutline);
+    }
+
+    private static void renderFilled(WorldRenderContext context) {
+        if (!MobHighlight.mobHighlight) return;
+        if (!MobHighlight.renderFilled()) return;
+        Vec3d camera = context.worldState().cameraRenderState.pos;
+        MatrixStack matrices = context.matrices();
+        if (matrices == null) return;
+        matrices.push();
+        matrices.translate(-camera.x, -camera.y, -camera.z);
+
+        VertexConsumerProvider consumers = context.consumers();
+        if (consumers == null) return;
+        VertexConsumer filledConsumer = consumers.getBuffer(RenderLayers.FILLED_ENTITY_LAYER);
+
+        double tickProgress = MinecraftClient.getInstance().getRenderTickCounter().getTickProgress(false);
+
+        float[] rgba = RenderUtils.toFloats(MobHighlight.batFilledColor);
+        bats.forEach(entity -> {
+
+            Vec3d pos = Misc.getPos(entity, tickProgress);
+
+            EntityDimensions dimension = entity.getDimensions(entity.getPose());
+            Box box = dimension.getBoxAt(pos);
+
+            VertexRendering.drawFilledBox(matrices, filledConsumer, box.minX, box.minY, box.minZ, box.maxX, box.maxY, box.maxZ, rgba[0], rgba[1], rgba[2], rgba[3]);
+        });
+
+        matrices.pop();
+
+    }
+
+    private static void renderOutline(WorldRenderContext context) {
+        if (!MobHighlight.mobHighlight) return;
+        if (!MobHighlight.renderOutline()) return;
+        Vec3d camera = context.worldState().cameraRenderState.pos;
+        MatrixStack matrices = context.matrices();
+        if (matrices == null) return;
+        matrices.push();
+        matrices.translate(-camera.x, -camera.y, -camera.z);
+
+        VertexConsumerProvider consumers = context.consumers();
+        if (consumers == null) return;
+        VertexConsumer outlineConsumer = consumers.getBuffer(RenderLayers.getOutline(MobHighlight.outlineWidth));
+
+        double tickProgress = MinecraftClient.getInstance().getRenderTickCounter().getTickProgress(false);
+
+        MatrixStack.Entry entry = matrices.peek();
+        float[] rgba = RenderUtils.toFloats(MobHighlight.batOutlineColor);
+
+        bats.forEach(entity -> {
+
+            Vec3d pos = Misc.getPos(entity, tickProgress);
+
+            EntityDimensions dimension = entity.getDimensions(entity.getPose());
+            Box box = dimension.getBoxAt(pos);
+
+            VertexRendering.drawBox(entry, outlineConsumer, box, rgba[0], rgba[1], rgba[2], rgba[3]);
+        });
+
+        matrices.pop();
+
+    }
+
+}
diff --git a/src/main/java/blade/addon/features/highlight/MobHighlight.java b/src/main/java/blade/addon/features/highlight/MobHighlight.java
index eec60bb..3afe97e 100644
--- a/src/main/java/blade/addon/features/highlight/MobHighlight.java
+++ b/src/main/java/blade/addon/features/highlight/MobHighlight.java
@@ -3,33 +3,29 @@ package blade.addon.features.highlight;
 import blade.addon.utils.Location;
 import blade.addon.utils.Misc;
 import blade.addon.utils.data.EntityUtil;
-import blade.addon.utils.dungeon.Phase;
 import blade.addon.utils.events.Events;
+import blade.addon.utils.interfaces.ArmourStandHolder;
 import blade.addon.utils.rendering.RenderLayers;
 import blade.addon.utils.rendering.RenderUtils;
 import config.practical.manager.ConfigValue;
-import net.fabricmc.fabric.api.client.event.lifecycle.v1.ClientEntityEvents;
 import net.fabricmc.fabric.api.client.event.lifecycle.v1.ClientTickEvents;
 import net.fabricmc.fabric.api.client.rendering.v1.world.WorldRenderContext;
 import net.fabricmc.fabric.api.client.rendering.v1.world.WorldRenderEvents;
 import net.minecraft.client.MinecraftClient;
-import net.minecraft.client.network.ClientPlayNetworkHandler;
 import net.minecraft.client.network.ClientPlayerEntity;
-import net.minecraft.client.network.PlayerListEntry;
 import net.minecraft.client.render.VertexConsumer;
 import net.minecraft.client.render.VertexConsumerProvider;
 import net.minecraft.client.render.VertexRendering;
 import net.minecraft.client.util.math.MatrixStack;
+import net.minecraft.client.world.ClientWorld;
 import net.minecraft.entity.Entity;
 import net.minecraft.entity.EntityDimensions;
-import net.minecraft.entity.boss.WitherEntity;
 import net.minecraft.entity.decoration.ArmorStandEntity;
 import net.minecraft.entity.effect.StatusEffects;
 import net.minecraft.entity.mob.EndermanEntity;
 import net.minecraft.entity.mob.SkeletonEntity;
 import net.minecraft.entity.mob.WitherSkeletonEntity;
 import net.minecraft.entity.mob.ZombieEntity;
-import net.minecraft.entity.passive.BatEntity;
 import net.minecraft.entity.player.PlayerEntity;
 import net.minecraft.item.Item;
 import net.minecraft.item.ItemStack;
@@ -40,17 +36,17 @@ import net.minecraft.util.math.Box;
 import net.minecraft.util.math.Vec3d;
 
 import java.util.ArrayList;
-import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentLinkedQueue;
 import java.util.function.Predicate;
-import java.util.regex.Matcher;
-import java.util.regex.Pattern;
 
 public class MobHighlight {
 
-    enum MobType {
-        STAR, TANK, MINI, FEL, ASSASSIN, BAT, WITHER, MIMIC
+    public enum MobType {
+        STAR, TANK, MINI, FEL, ASSASSIN, MIMIC
     }
 
+    record DataHolder(Entity entity, MobHighlight.MobType type) {}
+
     public enum HighlightType {
         FILLED("Filled"), OUTLINE("Outline"), BOTH("Both");
 
@@ -70,17 +66,8 @@ public class MobHighlight {
     private static final int PLAYER_HEAD_ID = Item.getRawId(Items.PLAYER_HEAD);
     private static final int LEATHER_BOOTS_ID = Item.getRawId(Items.LEATHER_BOOTS);
 
-    private static final Pattern MIMIC_PATTERN = Pattern.compile("Mimic");
-    private static final Pattern WEAPON_PATTERN = Pattern.compile("^Silent Death$");
-    private static final Pattern FEL_PATTERN = Pattern.compile("Fel");
-
-    private static final float WITHER_BORN_HEALTH = 300f;
-    private static final float[] BAT_HEALTHS = {100.0f, 200.0f, 400.0f, 800.0f};
-    private static final String[] TANK_MOBS = {"Zombie Commander", "Zombie Lord", "Skeleton Lord", "Withermancer", "Super Archer"};
-    private static final String[] MINI_BOSSES = {"Lost Adventurer", "Angry Archaeologist", "Frozen Adventurer"};
+    private static final ConcurrentLinkedQueue<DataHolder> savedEntities = new ConcurrentLinkedQueue<>();
 
-    private static final ConcurrentHashMap<Entity, MobType> trackedMobs = new ConcurrentHashMap<>();
-    private static final ConcurrentHashMap<Entity, Integer> possibleEntities = new ConcurrentHashMap<>();
 
     private static boolean dontRenderHighlight = false;
     @ConfigValue
@@ -142,93 +129,75 @@ public class MobHighlight {
 
 
     public static void init() {
-        Events.ON_ENTITY_TRACKED.register((entity, world) -> {
-            if (!Location.inDungeon() || !mobHighlight) return false;
+        ClientTickEvents.END_CLIENT_TICK.register(client -> {
+            if (!Location.inDungeon()) return;
 
-            if (Phase.inBoss()) {
-                if (entity instanceof WitherEntity wither) {
-                    if (wither.getHealth() != WITHER_BORN_HEALTH) {
-                        trackedMobs.put(wither, MobType.WITHER);
-                    }
-                }
-            } else {
+            ClientWorld world = client.world;
+            if (world == null) return;
+            for (Entity entity: world.getEntities()) {
                 if (entity instanceof ArmorStandEntity armorStand) {
-                    testArmourStand(armorStand);
-                }
-
-                if (entity instanceof BatEntity bat) {
-                    for (float health : BAT_HEALTHS) {
-                        if (health == bat.getHealth()) {
-                            trackedMobs.put(bat, MobType.BAT);
-                        }
-                    }
+                    ArmourStandHolder holder = (ArmourStandHolder) armorStand;
+                    if (holder.blade_addons$hasBeenScanned()) continue;
+                    holder.blade_addons$setScanned(true);
+                    testArmourStand(armorStand, client);
                 }
             }
 
+            savedEntities.removeIf(dataHolder -> dataHolder.entity.isRemoved());
+        });
+
+        Events.ON_ENTITY_TRACKED.register((entity, world) -> {
+            if (!Location.inDungeon()) return false;
+
             if (entity instanceof PlayerEntity player) {
-                if (isShadowAssassin(player)) {
-                    trackedMobs.put(player, MobType.ASSASSIN);
+                if (MobHighlight.isShadowAssassin(player)) {
+                    savedEntities.add(new DataHolder(player, MobType.ASSASSIN));
                 }
             }
 
             return false;
         });
 
+        Events.ON_LOCATION_CHANGE.register(newLocation -> {
+            savedEntities.clear();
+            return false;
+        });
+
         ClientTickEvents.END_CLIENT_TICK.register(minecraftClient -> {
             ClientPlayerEntity player = minecraftClient.player;
             if (player == null) return;
             dontRenderHighlight = player.hasStatusEffect(StatusEffects.BLINDNESS);
         });
 
-        ClientEntityEvents.ENTITY_LOAD.register(((entity, world) -> {
-            if (entity instanceof PlayerEntity player) {
-                if (isARealPlayer(player)) return;
-            }
-
-            if (isAPossibleStaredMob(entity) || entity instanceof EndermanEntity) {
-                possibleEntities.put(entity, 0);
-            }
-        }));
-
-        ClientEntityEvents.ENTITY_UNLOAD.register((entity, world) -> {
-            if (entity == null) return;
-            trackedMobs.remove(entity);
-            possibleEntities.remove(entity);
-        });
-
-        Events.ON_LOCATION_CHANGE.register(newLocation -> {
-            trackedMobs.clear();
-            possibleEntities.clear();
-            return false;
-        });
-
         WorldRenderEvents.AFTER_ENTITIES.register(MobHighlight::renderFilled);
         WorldRenderEvents.AFTER_ENTITIES.register(MobHighlight::renderOutline);
     }
 
-    private static void testArmourStand(ArmorStandEntity armorStand) {
+    public static void testArmourStand(ArmorStandEntity armorStand, MinecraftClient client) {
         Text text = armorStand.getCustomName();
         if (text == null) return;
-        String string = text.getString();
+        String name = text.getString();
+
+        ClientWorld world = client.world;
+        if (world == null) return;
 
-        Matcher matcher = MIMIC_PATTERN.matcher(string);
-        if (matcher.find()) {
-            attemptAddMob(armorStand, MobHighlight::isAPossibleMimic, 3.5, MobType.MIMIC);
+        if (name.contains("Mimic")) {
+            attemptAddMob(armorStand, MobHighlight::isAPossibleMimic, 3.5, MobHighlight.MobType.MIMIC, world);
             return;
         }
 
-        if (containsStarText(text)) {
-            matcher = FEL_PATTERN.matcher(string);
-            if (matcher.find()) {
-                attemptAddMob(armorStand, entity -> entity instanceof EndermanEntity, 4, MobType.FEL);
-            } else if (isMiniBoss(string)) {
-                attemptAddMob(armorStand, MobHighlight::isMiniBoss, 4, MobType.MINI);
-            } else if (isTankMob(string)) {
-                attemptAddMob(armorStand, MobHighlight::isAPossibleStaredMob, 3, MobType.TANK);
-            } else {
-                attemptAddMob(armorStand, MobHighlight::isAPossibleStaredMob, 3, MobType.STAR);
-            }
+        if (!MobHighlight.containsStarText(text)) return;
+
+        if (name.contains("Fel")) {
+            attemptAddMob(armorStand, entity -> entity instanceof EndermanEntity, 4, MobHighlight.MobType.FEL, world);
+        } else if (isMiniBoss(name)) {
+            attemptAddMob(armorStand, MobHighlight::isMiniBoss, 4, MobHighlight.MobType.MINI, world);
+        } else if (isTankMob(name)) {
+            attemptAddMob(armorStand, MobHighlight::isAPossibleStaredMob, 3, MobHighlight.MobType.TANK, world);
+        } else {
+            attemptAddMob(armorStand, MobHighlight::isAPossibleStaredMob, 3, MobHighlight.MobType.STAR, world);
         }
+
     }
 
     public static boolean containsStarText(Text text) {
@@ -245,23 +214,15 @@ public class MobHighlight {
         return false;
     }
 
-    public static boolean isTankMob(String string) {
-        for (String tank : TANK_MOBS) {
-            if (string.contains(tank)) return true;
-        }
-
-        return false;
+    public static boolean isTankMob(String name) {
+        return name.contains("Zombie Commander") || name.contains("Zombie Lord") || name.contains("Skeleton Lord")|| name.contains("Withermancer") || name.contains("Super Archer");
     }
 
-    public static boolean isMiniBoss(String string) {
-        for (String mini : MINI_BOSSES) {
-            if (string.contains(mini)) return true;
-        }
-
-        return false;
+    public static boolean isMiniBoss(String name) {
+        return name.contains("Lost Adventurer") || name.contains("Angry Archaeologist") || name.contains("Frozen Adventurer");
     }
 
-    private static void attemptAddMob(ArmorStandEntity armorStand, Predicate<Entity> requirements, double maxDistance, MobType mobType) {
+    public static void attemptAddMob(ArmorStandEntity armorStand, Predicate<Entity> requirements, double maxDistance, MobHighlight.MobType mobType, ClientWorld world) {
         Entity closest = null;
         double smallestDistance = maxDistance;
         double maxY = armorStand.getY();
@@ -270,7 +231,8 @@ public class MobHighlight {
 
         ArrayList<Entity> meetsRequirements = new ArrayList<>();
 
-        possibleEntities.forEach((entity, integer) -> {
+        Iterable<Entity> entities = world.getEntities();
+        entities.forEach(entity -> {
             if (!entity.getBoundingBox().intersects(boundingBox)) return;
             if (!requirements.test(entity)) return;
             if (entity.getY() > maxY) return;
@@ -279,7 +241,6 @@ public class MobHighlight {
         });
 
         for (Entity current : meetsRequirements) {
-            if (trackedMobs.containsKey(current)) continue;
             if (!current.getBoundingBox().intersects(boundingBox)) continue;
             if (!requirements.test(current)) continue;
             if (current.getY() > maxY) continue;
@@ -296,43 +257,22 @@ public class MobHighlight {
             return;
         }
 
-        possibleEntities.remove(closest);
-        trackedMobs.put(closest, mobType);
+        savedEntities.add(new DataHolder(closest, mobType));
     }
 
-    private static boolean isShadowAssassin(PlayerEntity player) {
-        if (isARealPlayer(player)) return false;
+    public static boolean isShadowAssassin(PlayerEntity player) {
+        if (EntityUtil.isARealPlayer(player)) return false;
         ItemStack heldItem = player.getMainHandStack();
         ItemStack boots = player.getInventory().getStack(36);
         Text text = heldItem.getCustomName();
 
         if (text == null) return false;
-        Matcher matcher = WEAPON_PATTERN.matcher(text.getString());
-        if (!matcher.find()) return false;
+        if (!text.getString().equals("Silent Death")) return false;
 
         return Item.getRawId(boots.getItem()) == LEATHER_BOOTS_ID;
     }
 
-    private static boolean isARealPlayer(Entity entity) {
-        if (entity instanceof PlayerEntity player) {
-
-            if (EntityUtil.isClientPlayer(player)) return true;
-
-            ClientPlayNetworkHandler networkHandler = MinecraftClient.getInstance().getNetworkHandler();
-            if (networkHandler == null) return false;
-
-            PlayerListEntry entry = networkHandler.getPlayerListEntry(player.getUuid());
-
-            //this is a hack which will fail if someone has a really old bugged ign that includes a space
-            if (entry != null) {
-                String name = entry.getProfile().name();
-                return !name.isEmpty() && !name.contains(" ");
-            }
-        }
-        return false;
-    }
-
-    private static boolean isAPossibleStaredMob(Entity entity) {
+    public static boolean isAPossibleStaredMob(Entity entity) {
         return switch (entity) {
             case ZombieEntity ignored -> true;
             case SkeletonEntity ignored -> true;
@@ -342,15 +282,16 @@ public class MobHighlight {
         };
     }
 
-    private static boolean isAPossibleMimic(Entity entity) {
+    public static boolean isAPossibleMimic(Entity entity) {
         if (entity instanceof ZombieEntity zombie) {
             return zombie.isBaby();
         }
         return false;
     }
 
-    private static boolean isMiniBoss(Entity entity) {
+    public static boolean isMiniBoss(Entity entity) {
         if (entity instanceof PlayerEntity player) {
+            if (EntityUtil.isARealPlayer(player)) return false;
             ItemStack helmet = player.getInventory().getStack(39);
             int id = Item.getRawId(helmet.getItem());
             return id == DIAMOND_HELMET_ID || id == PLAYER_HEAD_ID;
@@ -359,39 +300,28 @@ public class MobHighlight {
         return false;
     }
 
-    private static int getFilledColor(MobType mob) {
+    public static int getFilledColor(MobType mob) {
         return switch (mob) {
             case STAR -> starFilledColor;
             case TANK -> tankFilledColor;
             case MINI -> miniFilledColor;
             case FEL -> felFilledColor;
             case ASSASSIN -> assassinFilledColor;
-            case BAT -> batFilledColor;
-            case WITHER -> witherFilledColor;
             case MIMIC -> mimicFilledColor;
         };
     }
 
-    private static int getOutLineColor(MobType mob) {
+    public static int getOutlineColor(MobType mob) {
         return switch (mob) {
             case STAR -> starOutlineColor;
             case TANK -> tankOutlineColor;
             case MINI -> miniOutlineColor;
             case FEL -> felOutlineColor;
             case ASSASSIN -> assassinOutlineColor;
-            case BAT -> batOutlineColor;
-            case WITHER -> witherOutlineColor;
             case MIMIC -> mimicOutlineColor;
         };
     }
 
-    public static int getFilledColor(Entity entity) {
-        return getFilledColor(trackedMobs.get(entity));
-    }
-
-    public static int getOutlineColor(Entity entity) {
-        return getOutLineColor(trackedMobs.get(entity));
-    }
 
     public static boolean renderFilled() {
         return currentHighlight == HighlightType.BOTH || currentHighlight == HighlightType.FILLED;
@@ -401,14 +331,10 @@ public class MobHighlight {
         return currentHighlight == HighlightType.BOTH || currentHighlight == HighlightType.OUTLINE;
     }
 
-    private static Box getBox(Entity entity, Vec3d pos) {
+    public static Box getBox(Entity entity, Vec3d pos) {
         EntityDimensions dimension = entity.getDimensions(entity.getPose());
         Box box = dimension.getBoxAt(pos);
 
-        if (entity instanceof WitherEntity) {
-            box = box.expand(MobHighlight.witherExtraWidth, 0, MobHighlight.witherExtraWidth);
-        }
-
         //only shows the head
         if (entity instanceof EndermanEntity && entity.isInvisible() && MobHighlight.dontShowInvisibleMobs) {
             box = box.expand(0, -1.8, 0).offset(0, -1.2, 0);
@@ -439,7 +365,10 @@ public class MobHighlight {
 
         double tickProgress = MinecraftClient.getInstance().getRenderTickCounter().getTickProgress(false);
 
-        trackedMobs.forEach((entity, mobType) -> {
+        savedEntities.forEach(dataHolder -> {
+
+            Entity entity = dataHolder.entity;
+            MobType type = dataHolder.type;
 
             if (entity.isInvisible() && MobHighlight.dontShowInvisibleMobs && entity instanceof PlayerEntity) return;
 
@@ -448,7 +377,7 @@ public class MobHighlight {
             Box box = getBox(entity, pos);
 
 
-            int filledColor = MobHighlight.getFilledColor(entity);
+            int filledColor = getFilledColor(type);
             float[] rgba = RenderUtils.toFloats(filledColor);
             VertexRendering.drawFilledBox(matrices, filledConsumer, box.minX, box.minY, box.minZ, box.maxX, box.maxY, box.maxZ, rgba[0], rgba[1], rgba[2], rgba[3]);
         });
@@ -475,14 +404,17 @@ public class MobHighlight {
 
         MatrixStack.Entry entry = matrices.peek();
 
-        trackedMobs.forEach((entity, mobType) -> {
+        savedEntities.forEach(dataHolder -> {
+
+            Entity entity = dataHolder.entity;
+            MobType type = dataHolder.type;
 
             if (entity.isInvisible() && MobHighlight.dontShowInvisibleMobs && entity instanceof PlayerEntity) return;
 
             Vec3d pos = Misc.getPos(entity, tickProgress);
             Box box = getBox(entity, pos);
 
-            int outlineColor = MobHighlight.getOutlineColor(entity);
+            int outlineColor = getOutlineColor(type);
             float[] rgba = RenderUtils.toFloats(outlineColor);
             VertexRendering.drawBox(entry, outlineConsumer, box, rgba[0], rgba[1], rgba[2], rgba[3]);
         });
diff --git a/src/main/java/blade/addon/features/highlight/SheepHighlight.java b/src/main/java/blade/addon/features/highlight/SheepHighlight.java
index d55d834..56bd5ea 100644
--- a/src/main/java/blade/addon/features/highlight/SheepHighlight.java
+++ b/src/main/java/blade/addon/features/highlight/SheepHighlight.java
@@ -26,7 +26,7 @@ public class SheepHighlight {
 
     public static void init() {
 
-        Events.ON_ENTITY_TRACKED.register((entity, world) -> {
+        Events.ON_ENTITY_SPAWNED.register((entity, world) -> {
             if (!Location.inDungeon()) return false;
             if (entity instanceof SheepEntity sheep) {
                 sheepList.add(sheep);
@@ -39,7 +39,7 @@ public class SheepHighlight {
             return false;
         });
 
-        ClientTickEvents.END_CLIENT_TICK.register(client -> sheepList.removeIf(SheepEntity::isRemoved));
+        ClientTickEvents.END_CLIENT_TICK.register(client -> sheepList.removeIf(sheep -> sheep.isRemoved() || sheep.isDead()));
 
         WorldRenderEvents.AFTER_ENTITIES.register(SheepHighlight::renderFilled);
         WorldRenderEvents.AFTER_ENTITIES.register(SheepHighlight::renderOutline);
diff --git a/src/main/java/blade/addon/features/highlight/WitherHighlight.java b/src/main/java/blade/addon/features/highlight/WitherHighlight.java
new file mode 100644
index 0000000..c8f0dd6
--- /dev/null
+++ b/src/main/java/blade/addon/features/highlight/WitherHighlight.java
@@ -0,0 +1,119 @@
+package blade.addon.features.highlight;
+
+import blade.addon.utils.Location;
+import blade.addon.utils.Misc;
+import blade.addon.utils.dungeon.Phase;
+import blade.addon.utils.events.Events;
+import blade.addon.utils.rendering.RenderLayers;
+import blade.addon.utils.rendering.RenderUtils;
+import net.fabricmc.fabric.api.client.event.lifecycle.v1.ClientTickEvents;
+import net.fabricmc.fabric.api.client.rendering.v1.world.WorldRenderContext;
+import net.fabricmc.fabric.api.client.rendering.v1.world.WorldRenderEvents;
+import net.minecraft.client.MinecraftClient;
+import net.minecraft.client.render.VertexConsumer;
+import net.minecraft.client.render.VertexConsumerProvider;
+import net.minecraft.client.render.VertexRendering;
+import net.minecraft.client.util.math.MatrixStack;
+import net.minecraft.entity.EntityDimensions;
+import net.minecraft.entity.boss.WitherEntity;
+import net.minecraft.util.math.Box;
+import net.minecraft.util.math.Vec3d;
+
+import java.util.concurrent.ConcurrentLinkedQueue;
+
+public class WitherHighlight {
+
+    private static final float WITHER_BORN_HEALTH = 300f;
+
+    private static final ConcurrentLinkedQueue<WitherEntity> withers = new ConcurrentLinkedQueue<>();
+
+
+    public static void init() {
+        Events.ON_ENTITY_TRACKED.register((entity, world) -> {
+            if (!Location.inDungeon() || !MobHighlight.mobHighlight) return false;
+
+            if (Phase.inBoss()) {
+                if (entity instanceof WitherEntity wither) {
+                    if (wither.getHealth() != WITHER_BORN_HEALTH) {
+                        withers.add(wither);
+                    }
+                }
+            }
+
+            return false;
+        });
+
+        Events.ON_LOCATION_CHANGE.register(location -> {
+            withers.clear();
+            return false;
+        });
+
+        ClientTickEvents.END_CLIENT_TICK.register(client -> withers.removeIf(wither -> wither.isRemoved() || wither.isDead()));
+
+        WorldRenderEvents.AFTER_ENTITIES.register(WitherHighlight::renderFilled);
+        WorldRenderEvents.AFTER_ENTITIES.register(WitherHighlight::renderOutline);
+    }
+
+    private static void renderFilled(WorldRenderContext context) {
+        if (!MobHighlight.mobHighlight) return;
+        if (!MobHighlight.renderFilled()) return;
+        Vec3d camera = context.worldState().cameraRenderState.pos;
+        MatrixStack matrices = context.matrices();
+        if (matrices == null) return;
+        matrices.push();
+        matrices.translate(-camera.x, -camera.y, -camera.z);
+
+        VertexConsumerProvider consumers = context.consumers();
+        if (consumers == null) return;
+        VertexConsumer filledConsumer = consumers.getBuffer(RenderLayers.FILLED_ENTITY_LAYER);
+
+        double tickProgress = MinecraftClient.getInstance().getRenderTickCounter().getTickProgress(false);
+
+        float[] rgba = RenderUtils.toFloats(MobHighlight.witherFilledColor);
+        withers.forEach(entity -> {
+
+            Vec3d pos = Misc.getPos(entity, tickProgress);
+
+            EntityDimensions dimension = entity.getDimensions(entity.getPose());
+            Box box = dimension.getBoxAt(pos).expand(MobHighlight.witherExtraWidth, 0, MobHighlight.witherExtraWidth);
+
+            VertexRendering.drawFilledBox(matrices, filledConsumer, box.minX, box.minY, box.minZ, box.maxX, box.maxY, box.maxZ, rgba[0], rgba[1], rgba[2], rgba[3]);
+        });
+
+        matrices.pop();
+
+    }
+
+    private static void renderOutline(WorldRenderContext context) {
+        if (!MobHighlight.mobHighlight) return;
+        if (!MobHighlight.renderOutline()) return;
+        Vec3d camera = context.worldState().cameraRenderState.pos;
+        MatrixStack matrices = context.matrices();
+        if (matrices == null) return;
+        matrices.push();
+        matrices.translate(-camera.x, -camera.y, -camera.z);
+
+        VertexConsumerProvider consumers = context.consumers();
+        if (consumers == null) return;
+        VertexConsumer outlineConsumer = consumers.getBuffer(RenderLayers.getOutline(MobHighlight.outlineWidth));
+
+        double tickProgress = MinecraftClient.getInstance().getRenderTickCounter().getTickProgress(false);
+
+        MatrixStack.Entry entry = matrices.peek();
+        float[] rgba = RenderUtils.toFloats(MobHighlight.witherOutlineColor);
+
+        withers.forEach(entity -> {
+
+            Vec3d pos = Misc.getPos(entity, tickProgress);
+
+            EntityDimensions dimension = entity.getDimensions(entity.getPose());
+            Box box = dimension.getBoxAt(pos).expand(MobHighlight.witherExtraWidth, 0, MobHighlight.witherExtraWidth);
+
+            VertexRendering.drawBox(entry, outlineConsumer, box, rgba[0], rgba[1], rgba[2], rgba[3]);
+        });
+
+        matrices.pop();
+
+    }
+
+}
diff --git a/src/main/java/blade/addon/features/other/CompactHoppity.java b/src/main/java/blade/addon/features/other/CompactHoppity.java
index c63cb3d..e739a4b 100644
--- a/src/main/java/blade/addon/features/other/CompactHoppity.java
+++ b/src/main/java/blade/addon/features/other/CompactHoppity.java
@@ -1,7 +1,7 @@
 package blade.addon.features.other;
 
 import blade.addon.utils.Misc;
-import blade.addon.utils.config.values.ExtraOptions;
+import blade.addon.utils.config.values.Visual;
 import blade.addon.utils.events.Events;
 import net.minecraft.text.Text;
 
@@ -17,7 +17,7 @@ public class CompactHoppity {
     public static void init() {
 
         Events.ON_GAME_MESSAGE.register(text -> {
-            if (!ExtraOptions.compactHoppityMsgs) return false;
+            if (!Visual.compactHoppityMsgs) return false;
 
             String string = text.getString();
             if (string.equals("HOPPITY'S HUNT You found a Hitman Egg!")) {
diff --git a/src/main/java/blade/addon/features/other/PracticeSS.java b/src/main/java/blade/addon/features/other/PracticeSS.java
index e438314..85ee28d 100644
--- a/src/main/java/blade/addon/features/other/PracticeSS.java
+++ b/src/main/java/blade/addon/features/other/PracticeSS.java
@@ -128,13 +128,20 @@ public class PracticeSS {
         if (started && ExtraOptions.autoSkip) {
             start(world, state, pos);
             startPos = pos;
+            Misc.sendSound(ExtraOptions.ssSound);
         } else if (inSkipPhase || System.currentTimeMillis() - skippedTime < SKIP_TIME_MS) {
             clicks++;
             if (clicks >= 2 && inSkipPhase) {
                 skipped = true;
                 skippedTime = System.currentTimeMillis();
             }
+
+            if (clicks < 3) {
+                Misc.sendSound(ExtraOptions.ssSound);
+            }
+
         } else {
+            Misc.sendSound(ExtraOptions.ssSound);
             start(world, state, pos);
             startPos = pos;
         }
@@ -148,6 +155,8 @@ public class PracticeSS {
         BlockPos nextButton = buttons.get(currentIndex);
         if (samePosition(pos, nextButton)) {
             currentIndex++;
+            Misc.sendSound(ExtraOptions.ssSound);
+
 
             if (currentIndex == 5) {
                 recordTime();
diff --git a/src/main/java/blade/addon/mixin/ArmorStandEntityMixin.java b/src/main/java/blade/addon/mixin/ArmorStandEntityMixin.java
new file mode 100644
index 0000000..13ad612
--- /dev/null
+++ b/src/main/java/blade/addon/mixin/ArmorStandEntityMixin.java
@@ -0,0 +1,23 @@
+package blade.addon.mixin;
+
+import blade.addon.utils.interfaces.ArmourStandHolder;
+import net.minecraft.entity.decoration.ArmorStandEntity;
+import org.spongepowered.asm.mixin.Mixin;
+import org.spongepowered.asm.mixin.Unique;
+
+@Mixin(ArmorStandEntity.class)
+public class ArmorStandEntityMixin implements ArmourStandHolder {
+
+    @Unique
+    private boolean scanned = false;
+
+    @Override
+    public boolean blade_addons$hasBeenScanned() {
+        return scanned;
+    }
+
+    @Override
+    public void blade_addons$setScanned(boolean value) {
+        scanned = value;
+    }
+}
diff --git a/src/main/java/blade/addon/mixin/DrawContextMixin.java b/src/main/java/blade/addon/mixin/DrawContextMixin.java
new file mode 100644
index 0000000..2f77ea3
--- /dev/null
+++ b/src/main/java/blade/addon/mixin/DrawContextMixin.java
@@ -0,0 +1,17 @@
+package blade.addon.mixin;
+
+import blade.addon.utils.config.values.Visual;
+import net.minecraft.client.gui.DrawContext;
+import org.spongepowered.asm.mixin.Mixin;
+import org.spongepowered.asm.mixin.injection.At;
+import org.spongepowered.asm.mixin.injection.ModifyVariable;
+
+@Mixin(DrawContext.class)
+public class DrawContextMixin {
+
+   @ModifyVariable(method = "drawCooldownProgress", at=@At("STORE"), ordinal = 0)
+    private float noCooldown(float f) {
+        return Visual.hideCooldown? 0: f;
+    }
+
+}
diff --git a/src/main/java/blade/addon/mixin/EntityMixin.java b/src/main/java/blade/addon/mixin/EntityMixin.java
index 0369cde..de239dc 100644
--- a/src/main/java/blade/addon/mixin/EntityMixin.java
+++ b/src/main/java/blade/addon/mixin/EntityMixin.java
@@ -1,6 +1,6 @@
 package blade.addon.mixin;
 
-import blade.addon.utils.config.values.ExtraOptions;
+import blade.addon.utils.config.values.Visual;
 import net.minecraft.entity.Entity;
 import org.spongepowered.asm.mixin.Mixin;
 import org.spongepowered.asm.mixin.injection.At;
@@ -10,10 +10,10 @@ import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
 @Mixin(Entity.class)
 public class EntityMixin {
 
-    @Inject(method = "isGlowing", at=@At("HEAD"), cancellable = true)
+    @Inject(method = "isGlowing", at = @At("HEAD"), cancellable = true)
     public void isGlowing(CallbackInfoReturnable<Boolean> cir) {
-        if (ExtraOptions.disableGlowing) {
-                cir.setReturnValue(false);
+        if (Visual.disableGlowing) {
+            cir.setReturnValue(false);
         }
     }
 }
diff --git a/src/main/java/blade/addon/mixin/EntityRenderManagerMixin.java b/src/main/java/blade/addon/mixin/EntityRenderManagerMixin.java
index c7c570b..99f1f96 100644
--- a/src/main/java/blade/addon/mixin/EntityRenderManagerMixin.java
+++ b/src/main/java/blade/addon/mixin/EntityRenderManagerMixin.java
@@ -5,7 +5,7 @@ import blade.addon.features.dungeon.ItemHighlight;
 import blade.addon.features.highlight.MobHighlight;
 import blade.addon.utils.Location;
 import blade.addon.utils.config.values.Dungeons;
-import blade.addon.utils.config.values.ExtraOptions;
+import blade.addon.utils.config.values.Visual;
 import net.minecraft.client.MinecraftClient;
 import net.minecraft.client.network.ClientPlayerEntity;
 import net.minecraft.client.render.Frustum;
@@ -34,7 +34,7 @@ public class EntityRenderManagerMixin {
             }
         }
 
-        if (!entity.isAlive() && ExtraOptions.hideDeadEntities) {
+        if (!entity.isAlive() && Visual.hideDeadEntities) {
             cir.setReturnValue(false);
             return;
         }
diff --git a/src/main/java/blade/addon/mixin/EntityRendererMixin.java b/src/main/java/blade/addon/mixin/EntityRendererMixin.java
index 436a4db..5648763 100644
--- a/src/main/java/blade/addon/mixin/EntityRendererMixin.java
+++ b/src/main/java/blade/addon/mixin/EntityRendererMixin.java
@@ -1,7 +1,7 @@
 package blade.addon.mixin;
 
 import blade.addon.utils.config.values.Dungeons;
-import blade.addon.utils.config.values.ExtraOptions;
+import blade.addon.utils.config.values.Visual;
 import blade.addon.utils.data.EntityUtil;
 import net.minecraft.client.render.command.OrderedRenderCommandQueue;
 import net.minecraft.client.render.entity.EntityRenderer;
@@ -31,7 +31,12 @@ public class EntityRendererMixin<T extends Entity, S extends EntityRenderState>
     @Inject(method = "updateRenderState", at = @At("TAIL"))
     public void hideFire(T entity, S state, float tickProgress, CallbackInfo ci) {
 
-        if (entity instanceof PlayerEntity player && ExtraOptions.hideFireInf5) {
+        if (Visual.hideEntityFire) {
+            state.onFire = false;
+            return;
+        }
+
+        if (entity instanceof PlayerEntity player && Visual.hideFireInf5) {
             if (EntityUtil.isClientPlayer(player)) {
                 state.onFire = false;
             }
diff --git a/src/main/java/blade/addon/mixin/HandledScreenMixin.java b/src/main/java/blade/addon/mixin/HandledScreenMixin.java
index 34f780b..939bd03 100644
--- a/src/main/java/blade/addon/mixin/HandledScreenMixin.java
+++ b/src/main/java/blade/addon/mixin/HandledScreenMixin.java
@@ -7,6 +7,7 @@ import blade.addon.features.item.SelectedPetHighlight;
 import blade.addon.features.item.StarCountHighlight;
 import blade.addon.utils.config.values.Dungeons;
 import blade.addon.utils.config.values.ExtraOptions;
+import blade.addon.utils.config.values.Visual;
 import net.minecraft.client.gui.DrawContext;
 import net.minecraft.client.gui.screen.ingame.GenericContainerScreen;
 import net.minecraft.client.gui.screen.ingame.HandledScreen;
@@ -51,11 +52,11 @@ public class HandledScreenMixin<T extends ScreenHandler> {
             }
         }
 
-        if (ExtraOptions.highlightProtectedItem) {
+        if (Visual.highlightProtectedItem) {
             ProtectItem.draw(context, stack, x, y);
         }
 
-        if (ExtraOptions.itemRarityBackground) {
+        if (Visual.itemRarityBackground) {
             ItemRarityHighlight.draw(context, stack, x, y);
         }
     }
@@ -70,7 +71,7 @@ public class HandledScreenMixin<T extends ScreenHandler> {
             MaskHighlight.draw(context, stack, x, y);
         }
 
-        if (ExtraOptions.drawStarCount) {
+        if (Visual.drawStarCount) {
             StarCountHighlight.draw(context, stack, x, y);
         }
     }
diff --git a/src/main/java/blade/addon/mixin/InGameHudMixin.java b/src/main/java/blade/addon/mixin/InGameHudMixin.java
index 6e1452a..ca9c792 100644
--- a/src/main/java/blade/addon/mixin/InGameHudMixin.java
+++ b/src/main/java/blade/addon/mixin/InGameHudMixin.java
@@ -5,7 +5,7 @@ import blade.addon.features.dungeon.f7.terms.TitleHider;
 import blade.addon.features.item.ItemRarityHighlight;
 import blade.addon.features.item.ProtectItem;
 import blade.addon.features.item.StarCountHighlight;
-import blade.addon.utils.config.values.ExtraOptions;
+import blade.addon.utils.config.values.Visual;
 import blade.addon.utils.interfaces.GameHud;
 import net.minecraft.client.gui.DrawContext;
 import net.minecraft.client.gui.hud.InGameHud;
@@ -38,11 +38,11 @@ public class InGameHudMixin implements GameHud {
 
     @Inject(method = "renderHotbarItem", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/gui/DrawContext;drawItem(Lnet/minecraft/entity/LivingEntity;Lnet/minecraft/item/ItemStack;III)V"), order = 2000)
     public void drawBackground(DrawContext context, int x, int y, RenderTickCounter tickCounter, PlayerEntity player, ItemStack stack, int seed, CallbackInfo ci) {
-        if (ExtraOptions.itemRarityBackground) {
+        if (Visual.itemRarityBackground) {
             ItemRarityHighlight.draw(context, stack, x, y);
         }
 
-        if (ExtraOptions.highlightProtectedItem) {
+        if (Visual.highlightProtectedItem) {
             ProtectItem.draw(context, stack, x, y);
         }
 
@@ -50,14 +50,14 @@ public class InGameHudMixin implements GameHud {
 
     @Inject(method = "renderHotbarItem", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/gui/DrawContext;drawItem(Lnet/minecraft/entity/LivingEntity;Lnet/minecraft/item/ItemStack;III)V", shift = At.Shift.AFTER), order = 2000)
     public void drawStar(DrawContext context, int x, int y, RenderTickCounter tickCounter, PlayerEntity player, ItemStack stack, int seed, CallbackInfo ci) {
-        if (ExtraOptions.drawStarCount) {
+        if (Visual.drawStarCount) {
             StarCountHighlight.draw(context, stack, x, y);
         }
     }
 
     @Inject(method = "renderStatusEffectOverlay", at = @At("HEAD"), cancellable = true, order = 2000)
     public void renderStatusEffectsOverLay(DrawContext context, RenderTickCounter tickCounter, CallbackInfo ci) {
-        if (ExtraOptions.hideStatusOverLay) {
+        if (Visual.hideStatusOverLay) {
             ci.cancel();
         }
     }
diff --git a/src/main/java/blade/addon/mixin/StatusEffectsDisplayMixin.java b/src/main/java/blade/addon/mixin/StatusEffectsDisplayMixin.java
index dd6cb5f..34d76ef 100644
--- a/src/main/java/blade/addon/mixin/StatusEffectsDisplayMixin.java
+++ b/src/main/java/blade/addon/mixin/StatusEffectsDisplayMixin.java
@@ -1,6 +1,6 @@
 package blade.addon.mixin;
 
-import blade.addon.utils.config.values.ExtraOptions;
+import blade.addon.utils.config.values.Visual;
 import net.minecraft.client.gui.DrawContext;
 import net.minecraft.client.gui.screen.ingame.StatusEffectsDisplay;
 import org.spongepowered.asm.mixin.Mixin;
@@ -13,14 +13,14 @@ public class StatusEffectsDisplayMixin {
 
     @Inject(method = "drawStatusEffects", at=@At("HEAD"), cancellable = true)
     public void drawEffects(DrawContext context, int mouseX, int mouseY, CallbackInfo ci) {
-        if (ExtraOptions.hideStatusOverLay) {
+        if (Visual.hideStatusOverLay) {
             ci.cancel();
         }
     }
 
     @Inject(method = "drawStatusEffectTooltip", at=@At("HEAD"), cancellable = true)
     public void drawToolTip(DrawContext context, int mouseX, int mouseY, CallbackInfo ci) {
-        if (ExtraOptions.hideStatusOverLay) {
+        if (Visual.hideStatusOverLay) {
             ci.cancel();
         }
     }
diff --git a/src/main/java/blade/addon/mixin/StuckArrowsFeatureRendererMixin.java b/src/main/java/blade/addon/mixin/StuckArrowsFeatureRendererMixin.java
index 910bce5..cdd5f84 100644
--- a/src/main/java/blade/addon/mixin/StuckArrowsFeatureRendererMixin.java
+++ b/src/main/java/blade/addon/mixin/StuckArrowsFeatureRendererMixin.java
@@ -1,6 +1,6 @@
 package blade.addon.mixin;
 
-import blade.addon.utils.config.values.ExtraOptions;
+import blade.addon.utils.config.values.Visual;
 import net.minecraft.client.render.entity.feature.StuckArrowsFeatureRenderer;
 import org.spongepowered.asm.mixin.Mixin;
 import org.spongepowered.asm.mixin.injection.At;
@@ -12,7 +12,7 @@ public class StuckArrowsFeatureRendererMixin {
 
     @Inject(method = "getObjectCount", at=@At("TAIL"), cancellable = true)
     public void shouldRender(CallbackInfoReturnable<Integer> cir) {
-        if (ExtraOptions.hideStuckArrows) {
+        if (Visual.hideStuckArrows) {
             cir.setReturnValue(0);
         }
 
diff --git a/src/main/java/blade/addon/utils/config/Config.java b/src/main/java/blade/addon/utils/config/Config.java
index 388fba3..117670e 100644
--- a/src/main/java/blade/addon/utils/config/Config.java
+++ b/src/main/java/blade/addon/utils/config/Config.java
@@ -12,6 +12,7 @@ import blade.addon.utils.config.values.Buttons;
 import blade.addon.utils.config.values.Dungeons;
 import blade.addon.utils.config.values.ExtraOptions;
 import blade.addon.utils.config.values.Floor7;
+import blade.addon.utils.config.values.Visual;
 import blade.addon.utils.dungeon.Phase;
 import blade.addon.utils.dungeon.Section;
 import blade.addon.utils.dungeon.Split;
@@ -38,11 +39,29 @@ public class Config {
 
     private static final Text TITLE = Text.literal("Blade Addons");
     public static final ConfigManager manager = new ConfigManager(FolderUtility.OLD_PATH + FolderUtility.ADDONS_NAME,
-            List.of(Phase.class, Section.class, Split.class, MobHighlight.class, ExtraOptions.class, DianaNotifier.class, Components.class, Dungeons.class, Floor7.class, Buttons.class));
+            List.of(Phase.class, Section.class, Split.class, MobHighlight.class, ExtraOptions.class, DianaNotifier.class, Components.class, Dungeons.class, Floor7.class, Buttons.class, Visual.class));
 
     public static Screen createScreen(Screen parent) {
         ConfigurableScreen screen = new ConfigurableScreen(TITLE, parent, manager);
+        screen.addCategory(general());
+        screen.addCategory(dungeons());
+        screen.addCategory(floor7());
+        screen.addCategory(splits());
+        screen.addCategory(highlight());
+        screen.addCategory(extra());
+        screen.addCategory(visual());
+        screen.addCategory(buttons());
+        return screen;
+    }
 
+    private static ConfigCategory general() {
+        ConfigCategory general = new ConfigCategory("General");
+        general.add(new ConfigString(Text.literal("Message prefix"), () -> ExtraOptions.textPrefix, str -> ExtraOptions.textPrefix = str));
+        general.add(new ConfigButton(Text.literal("Edit Notifications"), () -> MinecraftClient.getInstance().setScreen(new NotificationList())));
+        return general;
+    }
+
+    private static ConfigCategory dungeons() {
         ConfigCategory dungeons = new ConfigCategory("Dungeons");
         ConfigSection start = new ConfigSection(Text.literal("Start of run"));
         start.add(new ConfigBool(Text.literal("Warp cooldown"), () -> Dungeons.enableWarpCooldown, bool -> Dungeons.enableWarpCooldown = bool));
@@ -103,9 +122,10 @@ public class Config {
         dungeons.add(new ConfigBool(Text.literal("Hide blaze nametags"), () -> Dungeons.hideBlazeNameTag, bool -> Dungeons.hideBlazeNameTag = bool));
         dungeons.add(new ConfigBool(Text.literal("Disable drop animation"), () -> Dungeons.disableDropAnimation, bool -> Dungeons.disableDropAnimation = bool));
         dungeons.add(new ConfigBool(Text.literal("Mask cooldown highlight"), () -> Dungeons.maskHighlight, bool -> Dungeons.maskHighlight = bool));
+        return dungeons;
+    }
 
-        screen.addCategory(dungeons);
-
+    private static ConfigCategory floor7() {
         ConfigCategory floor7 = new ConfigCategory("Floor 7");
         floor7.add(new ConfigBool(Text.literal("Combine tick timers"), () -> Floor7.combineTickTimers, bool -> Floor7.combineTickTimers = bool));
         floor7.add(new ConfigBool(Text.literal("Enable player leap count"), () -> Floor7.leapNotifications, bool -> Floor7.leapNotifications = bool));
@@ -173,8 +193,10 @@ public class Config {
 
         floor7.add(phase5);
 
-        screen.addCategory(floor7);
+        return floor7;
+    }
 
+    private static ConfigCategory splits() {
         ConfigCategory splits = new ConfigCategory("Splits");
         splits.add(new ConfigBool(Text.literal("Enable Splits"), () -> Phase.enableSplits, bool -> Phase.enableSplits = bool));
         splits.add(new ConfigBool(Text.literal("Include total time"), () -> Phase.includeTotalTime, bool -> Phase.includeTotalTime = bool));
@@ -194,8 +216,10 @@ public class Config {
         splits.add(new ConfigColor(Text.literal("Parentheses color (Inactive)"), () -> Split.parenthesesColorInactive, color -> Split.parenthesesColorInactive = color, "parentheses-inactive", false));
         splits.add(new ConfigColor(Text.literal("Parentheses color (Ongoing)"), () -> Split.parenthesesColorOngoing, color -> Split.parenthesesColorOngoing = color, "parentheses-ongoing", false));
         splits.add(new ConfigColor(Text.literal("Parentheses color (Complete)"), () -> Split.parenthesesColorComplete, color -> Split.parenthesesColorComplete = color, "parentheses-complete", false));
-        screen.addCategory(splits);
+        return splits;
+    }
 
+    private static ConfigCategory highlight() {
         ConfigCategory highlight = new ConfigCategory("Mob Highlight");
         highlight.add(new ConfigBool(Text.literal("Enable mob highlight"), () -> MobHighlight.mobHighlight, bool -> MobHighlight.mobHighlight = bool));
         highlight.add(new ConfigBool(Text.literal("Don't highlight invisible mobs"), () -> MobHighlight.dontShowInvisibleMobs, bool -> MobHighlight.dontShowInvisibleMobs = bool));
@@ -229,13 +253,12 @@ public class Config {
         sheep.add(new ConfigColor(Text.literal("Sheep filled color"), () -> MobHighlight.sheepFilledColor, color -> MobHighlight.sheepFilledColor = color, "sheep-filled", true));
         sheep.add(new ConfigColor(Text.literal("Sheep outline color"), () -> MobHighlight.sheepOutlineColor, color -> MobHighlight.sheepOutlineColor = color, "sheep-outline", true));
         highlight.add(sheep);
+        return highlight;
+    }
 
-        screen.addCategory(highlight);
-
+    private static ConfigCategory extra() {
 
         ConfigCategory extra = new ConfigCategory("Extra options");
-        extra.add(new ConfigString(Text.literal("Message prefix"), () -> ExtraOptions.textPrefix, str -> ExtraOptions.textPrefix = str));
-        extra.add(new ConfigButton(Text.literal("Edit Notifications"), () -> MinecraftClient.getInstance().setScreen(new NotificationList())));
         extra.add(new ConfigBool(Text.literal("Show pbs in chat"), () -> ExtraOptions.showPbs, bool -> ExtraOptions.showPbs = bool));
         extra.add(new ConfigBool(Text.literal("Disable scroll wheel in hotbar"), () -> ExtraOptions.disableScrollHotbar, bool -> ExtraOptions.disableScrollHotbar = bool));
         extra.add(new ConfigBool(Text.literal("Display kicked time"), () -> ExtraOptions.enableKickedTimer, bool -> ExtraOptions.enableKickedTimer = bool));
@@ -246,18 +269,6 @@ public class Config {
         sound.add(new ConfigBool(Text.literal("Disable bonzo sound"), () -> ExtraOptions.disableBonzoSound, bool -> ExtraOptions.disableBonzoSound = bool));
         extra.add(sound);
 
-        ConfigSection visual = new ConfigSection(Text.literal("Visual changes"));
-        visual.add(new ConfigBool(Text.literal("Disable fire in f5"), () -> ExtraOptions.hideFireInf5, bool -> ExtraOptions.hideFireInf5 = bool));
-        visual.add(new ConfigBool(Text.literal("Hide arrows stuck to entities"), () -> ExtraOptions.hideStuckArrows, bool -> ExtraOptions.hideStuckArrows = bool));
-        visual.add(new ConfigBool(Text.literal("Hide dead entities"), () -> ExtraOptions.hideDeadEntities, bool -> ExtraOptions.hideDeadEntities = bool));
-        visual.add(new ConfigBool(Text.literal("Item rarity background"), () -> ExtraOptions.itemRarityBackground, bool -> ExtraOptions.itemRarityBackground = bool));
-        visual.add(new ConfigBool(Text.literal("Hide potion effects overlay"), () -> ExtraOptions.hideStatusOverLay, bool -> ExtraOptions.hideStatusOverLay = bool));
-        visual.add(new ConfigBool(Text.literal("Disable glowing"), () -> ExtraOptions.disableGlowing, bool -> ExtraOptions.disableGlowing = bool));
-        visual.add(new ConfigBool(Text.literal("Draw item starCount"), () -> ExtraOptions.drawStarCount, bool -> ExtraOptions.drawStarCount = bool));
-        visual.add(new ConfigBool(Text.literal("Highlight protected items"), () -> ExtraOptions.highlightProtectedItem, bool -> ExtraOptions.highlightProtectedItem = bool));
-        visual.add(new ConfigBool(Text.literal("Compact hoppity messages"), () -> ExtraOptions.compactHoppityMsgs, bool -> ExtraOptions.compactHoppityMsgs = bool));
-        extra.add(visual);
-
         ConfigSection rag = new ConfigSection(Text.literal("Ragnarock"));
         rag.add(new ConfigBool(Text.literal("Display rag axe duration"), () -> ExtraOptions.enableRagaxeDisplay, bool -> ExtraOptions.enableRagaxeDisplay = bool));
         rag.add(new ConfigBool(Text.literal("Use 1.8.9 rag sound"), () -> ExtraOptions.useOldRagSound, bool -> ExtraOptions.useOldRagSound = bool));
@@ -299,10 +310,29 @@ public class Config {
         ss.add(new ConfigBool(Text.literal("Block unlucky button click"), () -> ExtraOptions.blockUnluckyButtonClick, bool -> ExtraOptions.blockUnluckyButtonClick = bool));
         ss.add(new ConfigDouble(Text.literal("Lucky button rng (0.1)"), () -> ExtraOptions.luckyButtonRng, num -> ExtraOptions.luckyButtonRng = num, 0.01, 0, 1));
         ss.add(new ConfigColor(Text.literal("Lucky button color"), () -> ExtraOptions.luckyButtonColor, color -> ExtraOptions.luckyButtonColor = color, "lucky-button-color", true));
+        ss.add(new ConfigSound(Text.literal("Button sound"), ExtraOptions.ssSound));
+
         extra.add(ss);
+        return extra;
+    }
 
-        screen.addCategory(extra);
+    private static ConfigCategory visual() {
+        ConfigCategory visual = new ConfigCategory("Visual changes");
+        visual.add(new ConfigBool(Text.literal("Disable fire in f5"), () -> Visual.hideFireInf5, bool -> Visual.hideFireInf5 = bool));
+        visual.add(new ConfigBool(Text.literal("Hide all entity on fire"), () -> Visual.hideEntityFire, bool -> Visual.hideEntityFire = bool));
+        visual.add(new ConfigBool(Text.literal("Hide arrows stuck to entities"), () -> Visual.hideStuckArrows, bool -> Visual.hideStuckArrows = bool));
+        visual.add(new ConfigBool(Text.literal("Hide dead entities"), () -> Visual.hideDeadEntities, bool -> Visual.hideDeadEntities = bool));
+        visual.add(new ConfigBool(Text.literal("Item rarity background"), () -> Visual.itemRarityBackground, bool -> Visual.itemRarityBackground = bool));
+        visual.add(new ConfigBool(Text.literal("Hide potion effects overlay"), () -> Visual.hideStatusOverLay, bool -> Visual.hideStatusOverLay = bool));
+        visual.add(new ConfigBool(Text.literal("Disable glowing"), () -> Visual.disableGlowing, bool -> Visual.disableGlowing = bool));
+        visual.add(new ConfigBool(Text.literal("Draw item starCount"), () -> Visual.drawStarCount, bool -> Visual.drawStarCount = bool));
+        visual.add(new ConfigBool(Text.literal("Highlight protected items"), () -> Visual.highlightProtectedItem, bool -> Visual.highlightProtectedItem = bool));
+        visual.add(new ConfigBool(Text.literal("Compact hoppity messages"), () -> Visual.compactHoppityMsgs, bool -> Visual.compactHoppityMsgs = bool));
+        visual.add(new ConfigBool(Text.literal("Hide cooldown"), () -> Visual.hideCooldown, bool -> Visual.hideCooldown = bool));
+        return visual;
+    }
 
+    private static ConfigCategory buttons() {
         ConfigCategory inventory = new ConfigCategory("Inventory buttons");
         inventory.add(new ConfigString(Text.literal("Button 1"), () -> Buttons.command1, str -> Buttons.command1 = str));
         inventory.add(new ConfigString(Text.literal("Button 2"), () -> Buttons.command2, str -> Buttons.command2 = str));
@@ -311,10 +341,7 @@ public class Config {
         inventory.add(new ConfigString(Text.literal("Button 5"), () -> Buttons.command5, str -> Buttons.command5 = str));
         inventory.add(new ConfigString(Text.literal("Button 6"), () -> Buttons.command6, str -> Buttons.command6 = str));
         inventory.add(new ConfigString(Text.literal("Button 7"), () -> Buttons.command7, str -> Buttons.command7 = str));
-
-        screen.addCategory(inventory);
-
-        return screen;
+        return inventory;
     }
 
 }
diff --git a/src/main/java/blade/addon/utils/config/values/ExtraOptions.java b/src/main/java/blade/addon/utils/config/values/ExtraOptions.java
index 72e9d05..1ef05d4 100644
--- a/src/main/java/blade/addon/utils/config/values/ExtraOptions.java
+++ b/src/main/java/blade/addon/utils/config/values/ExtraOptions.java
@@ -6,27 +6,9 @@ import net.minecraft.sound.SoundEvents;
 import net.minecraft.util.math.BlockPos;
 
 public class ExtraOptions {
-    @ConfigValue
-    public static boolean hideFireInf5 = false;
-
-    @ConfigValue
-    public static boolean hideStuckArrows = false;
-
     @ConfigValue
     public static boolean disableAbilityCooldownSound = true;
 
-    @ConfigValue
-    public static boolean hideDeadEntities = false;
-
-    @ConfigValue
-    public static boolean itemRarityBackground = false;
-
-    @ConfigValue
-    public static boolean hideStatusOverLay = false;
-
-    @ConfigValue
-    public static boolean disableGlowing = false;
-
     @ConfigValue
     public static String textPrefix = "";
 
@@ -39,12 +21,6 @@ public class ExtraOptions {
     @ConfigValue
     public static boolean includePetSprite = true;
 
-    @ConfigValue
-    public static boolean drawStarCount = false;
-
-    @ConfigValue
-    public static boolean highlightProtectedItem = false;
-
     @ConfigValue
     public static boolean showPbs = true;
 
@@ -78,9 +54,6 @@ public class ExtraOptions {
     @ConfigValue
     public static boolean blockUnluckyButtonClick = false;
 
-    @ConfigValue
-    public static boolean compactHoppityMsgs = false;
-
     @ConfigValue
     public static BlockPos startButton = new BlockPos(2, 2, 2);
 
@@ -116,4 +89,7 @@ public class ExtraOptions {
 
     @ConfigValue
     public static boolean useOldRagSound = false;
+
+    @ConfigValue
+    public static SoundData ssSound = new SoundData(SoundEvents.BLOCK_NOTE_BLOCK_PLING.value(), 0, 1);
 }
diff --git a/src/main/java/blade/addon/utils/config/values/Visual.java b/src/main/java/blade/addon/utils/config/values/Visual.java
new file mode 100644
index 0000000..b53cc5e
--- /dev/null
+++ b/src/main/java/blade/addon/utils/config/values/Visual.java
@@ -0,0 +1,40 @@
+package blade.addon.utils.config.values;
+
+import config.practical.manager.ConfigValue;
+
+public class Visual {
+
+    @ConfigValue
+    public static boolean hideFireInf5 = false;
+
+    @ConfigValue
+    public static boolean hideStuckArrows = false;
+
+    @ConfigValue
+    public static boolean hideDeadEntities = false;
+
+    @ConfigValue
+    public static boolean itemRarityBackground = false;
+
+    @ConfigValue
+    public static boolean hideStatusOverLay = false;
+
+    @ConfigValue
+    public static boolean disableG
…… diff 过长已截断 ……
Merge remote-tracking branch 'origin/new' into newGabriel Ellnebrand · 2026-01-11 · 7bea460
查看变更
README.md | 5 ++++-
 1 file changed, 4 insertions(+), 1 deletion(-)
hopefully fixed block incorrect relic place not working (needs actual testing) - hopefully fixed detecting other peoples placed relic time - fixed kickedTimer being triggered if someone sent the msg in chat (needs testing)
- added class colors for melody and location msgs (needs testing) - added chat notifications (fix § not being able to be inputed in configString) - added 1.8.9 rag sound toggle - added mute bonzo staffs ghast sound - did some refactoring
Gabriel Ellnebrand · 2026-01-11 · 5f460f5
查看变更
gradle.properties                                                                  |   2 +-
 src/main/java/blade/addon/Bladeaddons.java                                         |   6 ++-
 src/main/java/blade/addon/features/dungeon/HidePlayers.java                        |   5 +--
 src/main/java/blade/addon/features/dungeon/LeapOrder.java                          |   2 +-
 src/main/java/blade/addon/features/dungeon/f7/BossWaypoints.java                   |   3 +-
 src/main/java/blade/addon/features/dungeon/f7/CrystalSpawn.java                    |   3 +-
 src/main/java/blade/addon/features/dungeon/f7/RelicTimer.java                      | 114 ++++++++++++++++++++++++++++++++-----------------
 src/main/java/blade/addon/features/dungeon/f7/location/LocationNotifier.java       |  23 +++++++---
 src/main/java/blade/addon/features/dungeon/f7/location/PositionMessages.java       |   2 +-
 src/main/java/blade/addon/features/dungeon/f7/terms/LeapNotification.java          |   4 +-
 src/main/java/blade/addon/features/dungeon/f7/terms/MelodyWarning.java             |  20 +++++++--
 src/main/java/blade/addon/features/dungeon/f7/terms/Pre4Notifier.java              |   4 +-
 src/main/java/blade/addon/features/dungeon/f7/terms/TitleHider.java                |   3 +-
 src/main/java/blade/addon/features/highlight/MobHighlight.java                     |   3 +-
 src/main/java/blade/addon/features/item/ProtectItem.java                           |   4 +-
 src/main/java/blade/addon/features/notifications/Notification.java                 | 120 ++++++++++++++++++++++++++++++++++++++++++++++++++++
 src/main/java/blade/addon/features/notifications/NotificationEditScreen.java       |  55 ++++++++++++++++++++++++
 src/main/java/blade/addon/features/notifications/NotificationEntry.java            |  84 ++++++++++++++++++++++++++++++++++++
 src/main/java/blade/addon/features/notifications/NotificationList.java             |  89 ++++++++++++++++++++++++++++++++++++++
 src/main/java/blade/addon/features/notifications/Notifications.java                | 160 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 src/main/java/blade/addon/features/other/DianaNotifier.java                        |   2 +-
 src/main/java/blade/addon/features/other/KickedTimer.java                          |   2 +-
 src/main/java/blade/addon/features/other/RagDisplay.java                           |  10 ++++-
 src/main/java/blade/addon/features/other/SelectedPet.java                          |   2 +-
 src/main/java/blade/addon/mixin/ChatScreenMixin.java                               |  43 ++++++++++++++-----
 src/main/java/blade/addon/mixin/ClientPlayNetworkHandlerMixin.java                 |  32 +++++++++++---
 src/main/java/blade/addon/mixin/EntityRendererMixin.java                           |   4 +-
 src/main/java/blade/addon/mixin/HeldItemRendererMixin.java                         |   2 -
 src/main/java/blade/addon/mixin/InventoryScreenMixin.java                          |   7 ---
 src/main/java/blade/addon/mixin/PlayerEntityMixin.java                             |   2 +-
 src/main/java/blade/addon/utils/Commands.java                                      |   8 ----
 src/main/java/blade/addon/utils/Constants.java                                     |   2 +
 src/main/java/blade/addon/utils/Debug.java                                         |  82 -----------------------------------
 src/main/java/blade/addon/utils/JsonUtility.java                                   |   1 +
 src/main/java/blade/addon/utils/Location.java                                      |   1 +
 src/main/java/blade/addon/utils/Misc.java                                          |  93 +++++-----------------------------------
 src/main/java/blade/addon/utils/config/Config.java                                 |  55 +++++++++++++++++-------
 src/main/java/blade/addon/utils/config/FolderUtility.java                          |   3 +-
 src/main/java/blade/addon/utils/config/components/CombinedScreenNotifications.java |   5 +++
 src/main/java/blade/addon/utils/config/components/Components.java                  |   4 ++
 src/main/java/blade/addon/utils/config/values/Dungeons.java                        |  18 ++++++++
 src/main/java/blade/addon/utils/config/values/ExtraOptions.java                    |   6 +++
 src/main/java/blade/addon/utils/data/EntityUtil.java                               |  46 ++++++++++++++++++++
 src/main/java/blade/addon/utils/data/ItemUtil.java                                 |  27 ++++++++++++
 src/main/java/blade/addon/utils/data/PartyUtil.java                                |   2 +-
 src/main/java/blade/addon/utils/data/TextUtil.java                                 | 120 ++++++++++++++++++++++++++++++++++++++++++++++++++++
 src/main/java/blade/addon/utils/debug/Debug.java                                   | 112 ++++++++++++++++++++++++++++++++++++++++++++++++
 src/main/java/blade/addon/utils/detection/Detection.java                           |  17 --------
 src/main/java/blade/addon/utils/dungeon/DungeonClass.java                          |  17 ++++++++
 src/main/java/blade/addon/utils/dungeon/Phase.java                                 |   3 +-
 src/main/java/blade/addon/utils/dungeon/Section.java                               |   2 +-
 src/main/java/blade/addon/utils/dungeon/Split.java                                 |   4 ++
 src/main/java/blade/addon/utils/events/CustomEvents.java                           |   2 +-
 src/main/java/blade/addon/utils/interfaces/GameHud.java                            |   2 +-
 src/main/java/blade/addon/utils/{detection => interfaces}/PlayerDataHolder.java    |   2 +-
 src/main/java/blade/addon/utils/rendering/RenderUtils.java                         |   6 +++
 src/main/resources/assets/blade-addons/sounds.json                                 |   7 +++
 src/main/resources/assets/blade-addons/sounds/wolf-howl.ogg                        | Bin 0 -> 16994 bytes
 58 files changed, 1148 insertions(+), 311 deletions(-)
diff --git a/gradle.properties b/gradle.properties
index a392cc6..2791ec8 100644
--- a/gradle.properties
+++ b/gradle.properties
@@ -13,7 +13,7 @@ loader_version=0.18.4
 loom_version=1.14-SNAPSHOT
 
 # Mod Properties
-mod_version=2.3.0
+mod_version=2.3.1
 maven_group=blade.addon
 archives_base_name=blade-addons
 
diff --git a/src/main/java/blade/addon/Bladeaddons.java b/src/main/java/blade/addon/Bladeaddons.java
index faa90b1..0475cec 100644
--- a/src/main/java/blade/addon/Bladeaddons.java
+++ b/src/main/java/blade/addon/Bladeaddons.java
@@ -2,12 +2,12 @@ package blade.addon;
 
 import blade.addon.features.dungeon.AutoRequeue;
 import blade.addon.features.dungeon.ChestCounter;
-import blade.addon.features.dungeon.RunStartValidator;
 import blade.addon.features.dungeon.ExplosiveShot;
 import blade.addon.features.dungeon.HidePlayers;
 import blade.addon.features.dungeon.ItemHighlight;
 import blade.addon.features.dungeon.KeyNotifier;
 import blade.addon.features.dungeon.LeapMessage;
+import blade.addon.features.dungeon.RunStartValidator;
 import blade.addon.features.dungeon.SecretSpawnTimer;
 import blade.addon.features.dungeon.WarpCooldown;
 import blade.addon.features.dungeon.f7.BossWaypoints;
@@ -30,6 +30,7 @@ import blade.addon.features.highlight.MimicHighlight;
 import blade.addon.features.highlight.MobHighlight;
 import blade.addon.features.highlight.SheepHighlight;
 import blade.addon.features.item.DropAnimation;
+import blade.addon.features.notifications.Notifications;
 import blade.addon.features.other.CompactHoppity;
 import blade.addon.features.other.DianaNotifier;
 import blade.addon.features.other.KickedTimer;
@@ -37,7 +38,6 @@ import blade.addon.features.other.PracticeSS;
 import blade.addon.features.other.RagDisplay;
 import blade.addon.features.other.SelectedPet;
 import blade.addon.utils.Commands;
-import blade.addon.utils.Debug;
 import blade.addon.utils.Keybinds;
 import blade.addon.utils.Location;
 import blade.addon.utils.Scheduler;
@@ -46,6 +46,7 @@ import blade.addon.utils.config.FolderUtility;
 import blade.addon.utils.config.components.Components;
 import blade.addon.utils.config.values.Buttons;
 import blade.addon.utils.data.PartyUtil;
+import blade.addon.utils.debug.Debug;
 import blade.addon.utils.dungeon.DungeonClass;
 import blade.addon.utils.dungeon.Phase;
 import blade.addon.utils.dungeon.Section;
@@ -106,5 +107,6 @@ public class Bladeaddons implements ModInitializer {
         DropAnimation.init();
         MimicHighlight.init();
         SheepHighlight.init();
+        Notifications.init();
     }
 }
\ No newline at end of file
diff --git a/src/main/java/blade/addon/features/dungeon/HidePlayers.java b/src/main/java/blade/addon/features/dungeon/HidePlayers.java
index e9beacc..97ee2c2 100644
--- a/src/main/java/blade/addon/features/dungeon/HidePlayers.java
+++ b/src/main/java/blade/addon/features/dungeon/HidePlayers.java
@@ -1,9 +1,8 @@
 package blade.addon.features.dungeon;
 
 import blade.addon.utils.Location;
-import blade.addon.utils.Misc;
 import blade.addon.utils.config.values.Dungeons;
-import blade.addon.utils.detection.Detection;
+import blade.addon.utils.data.EntityUtil;
 import blade.addon.utils.dungeon.Phase;
 import blade.addon.utils.events.Events;
 import net.minecraft.client.MinecraftClient;
@@ -61,7 +60,7 @@ public class HidePlayers {
     }
 
     public static boolean shouldHidePlayers(PlayerEntity player) {
-        if (!Location.inDungeon() || !Detection.isARealPlayer(player) || Misc.isClientPlayer(player)) return false;
+        if (!Location.inDungeon() || !EntityUtil.isARealPlayer(player) || EntityUtil.isClientPlayer(player)) return false;
 
         if (testHideAtLeap()) {
             return true;
diff --git a/src/main/java/blade/addon/features/dungeon/LeapOrder.java b/src/main/java/blade/addon/features/dungeon/LeapOrder.java
index aebaf53..17feb7f 100644
--- a/src/main/java/blade/addon/features/dungeon/LeapOrder.java
+++ b/src/main/java/blade/addon/features/dungeon/LeapOrder.java
@@ -1,6 +1,6 @@
 package blade.addon.features.dungeon;
 
-import blade.addon.utils.Debug;
+import blade.addon.utils.debug.Debug;
 import blade.addon.utils.Misc;
 import blade.addon.utils.dungeon.DungeonClass;
 import net.minecraft.client.MinecraftClient;
diff --git a/src/main/java/blade/addon/features/dungeon/f7/BossWaypoints.java b/src/main/java/blade/addon/features/dungeon/f7/BossWaypoints.java
index 2a1ee5d..0ae44a9 100644
--- a/src/main/java/blade/addon/features/dungeon/f7/BossWaypoints.java
+++ b/src/main/java/blade/addon/features/dungeon/f7/BossWaypoints.java
@@ -5,6 +5,7 @@ import blade.addon.utils.Misc;
 import blade.addon.utils.Waypoint;
 import blade.addon.utils.config.FolderUtility;
 import blade.addon.utils.config.values.Floor7;
+import blade.addon.utils.data.EntityUtil;
 import blade.addon.utils.dungeon.Phase;
 import blade.addon.utils.rendering.RenderLayers;
 import blade.addon.utils.rendering.RenderUtils;
@@ -115,7 +116,7 @@ public class BossWaypoints {
     }
 
     private static ActionResult onBlock(PlayerEntity playerEntity, World world, Hand hand, BlockHitResult blockHitResult) {
-        if (!isInValidArea() || !Misc.isClientPlayer(playerEntity)) return ActionResult.PASS;
+        if (!isInValidArea() || !EntityUtil.isClientPlayer(playerEntity)) return ActionResult.PASS;
         if (hand == Hand.OFF_HAND) return ActionResult.PASS;
 
         BlockPos pos = blockHitResult.getBlockPos();
diff --git a/src/main/java/blade/addon/features/dungeon/f7/CrystalSpawn.java b/src/main/java/blade/addon/features/dungeon/f7/CrystalSpawn.java
index 94b84ec..ab8c0c2 100644
--- a/src/main/java/blade/addon/features/dungeon/f7/CrystalSpawn.java
+++ b/src/main/java/blade/addon/features/dungeon/f7/CrystalSpawn.java
@@ -4,6 +4,7 @@ import blade.addon.utils.Constants;
 import blade.addon.utils.Location;
 import blade.addon.utils.Misc;
 import blade.addon.utils.config.values.Floor7;
+import blade.addon.utils.data.EntityUtil;
 import blade.addon.utils.dungeon.Phase;
 import blade.addon.utils.events.Events;
 import blade.addon.utils.rendering.RenderUtils;
@@ -50,7 +51,7 @@ public class CrystalSpawn {
             matcher = RELIC_PICK_UP.matcher(text.getString());
             if (matcher.find()) {
                 String name = matcher.group(1);
-                if (Misc.isClientPlayer(name)) {
+                if (EntityUtil.isClientPlayer(name)) {
                     pickupTime = System.currentTimeMillis();
                     pickedUp = true;
                 }
diff --git a/src/main/java/blade/addon/features/dungeon/f7/RelicTimer.java b/src/main/java/blade/addon/features/dungeon/f7/RelicTimer.java
index c58cec4..b6f5948 100644
--- a/src/main/java/blade/addon/features/dungeon/f7/RelicTimer.java
+++ b/src/main/java/blade/addon/features/dungeon/f7/RelicTimer.java
@@ -4,12 +4,16 @@ import blade.addon.utils.Constants;
 import blade.addon.utils.Location;
 import blade.addon.utils.Misc;
 import blade.addon.utils.config.values.Floor7;
+import blade.addon.utils.data.EntityUtil;
+import blade.addon.utils.data.ItemUtil;
+import blade.addon.utils.debug.Debug;
 import blade.addon.utils.dungeon.Phase;
 import blade.addon.utils.events.Events;
 import blade.addon.utils.rendering.RenderLayers;
 import blade.addon.utils.rendering.RenderUtils;
 import blade.addon.utils.times.PersonalBests;
 import config.practical.hud.HUDComponent;
+import net.fabricmc.fabric.api.client.event.lifecycle.v1.ClientTickEvents;
 import net.fabricmc.fabric.api.client.message.v1.ClientReceiveMessageEvents;
 import net.fabricmc.fabric.api.client.rendering.v1.world.WorldRenderEvents;
 import net.minecraft.client.MinecraftClient;
@@ -19,9 +23,9 @@ import net.minecraft.client.render.VertexConsumer;
 import net.minecraft.client.render.VertexConsumerProvider;
 import net.minecraft.client.render.VertexRendering;
 import net.minecraft.client.util.math.MatrixStack;
+import net.minecraft.entity.Entity;
 import net.minecraft.entity.EquipmentSlot;
 import net.minecraft.entity.decoration.ArmorStandEntity;
-import net.minecraft.item.ItemStack;
 import net.minecraft.text.MutableText;
 import net.minecraft.text.Text;
 import net.minecraft.util.Formatting;
@@ -34,7 +38,7 @@ import java.util.regex.Pattern;
 
 public class RelicTimer {
 
-    enum Relic {
+    public enum Relic {
         GREEN(new Box(49, 7, 44, 50, 8, 45), 0xff55ff55, "§cRed"),
         RED(new Box(51, 7, 42, 52, 8, 43), 0xffff5555, "§aGreen"),
         PURPLE(new Box(54, 7, 41, 55, 8, 42), 0xffff55ff, "§5Purple"),
@@ -53,6 +57,8 @@ public class RelicTimer {
         }
     }
 
+    private static boolean ignoreChecks = false;
+
     private static final int GREEN_COLOR = 0xff00ff00;
     private static final int RED_COLOR = 0xffff0000;
 
@@ -82,26 +88,22 @@ public class RelicTimer {
 
         Events.ON_LOCATION_CHANGE.register(newLocation -> {
             if (Location.inDungeon()) {
-                tick = Floor7.relicSpawnTicks;
-                pickedupRelic = null;
-                for (Relic relic : Relic.values()) {
-                    relic.placedTime = 0;
-                }
+               reset();
             }
-
             return false;
         });
 
         Events.ON_PHASE_CHANGE.register(() -> {
             if (Phase.inP5()) {
                 phaseStartTime = System.currentTimeMillis();
+                tick = Floor7.relicSpawnTicks;
             }
             return false;
         });
 
         ClientReceiveMessageEvents.GAME.register((message, overlay) -> {
-            if (!Location.inDungeon() || !Phase.inP5() || !Floor7.enableRelicPlaceTime) return;
-
+            if (!Location.inDungeon() || !Phase.inP5()) return;
+            Debug.sendDebugMessage(Text.literal("Debug: ").append(message));
             String str = message.getString().replaceAll("§.", "");
             Matcher matcher = PATTERN.matcher(str);
             if (matcher.find()) {
@@ -112,48 +114,61 @@ public class RelicTimer {
                 if (player == null) return;
                 if (name.equals(player.getName().getString())) {
                     pickedupRelic = Relic.valueOf(relicString.toUpperCase());
+                    Debug.sendDebugMessage(Text.literal("Picked up relic " + relicString));
                 }
             }
         });
 
-        Events.ON_ENTITY_SPAWNED.register((entity, world) -> {
-            if (!Location.inDungeon() || !Phase.inP5() || !Floor7.showAllRelicTimes) return false;
 
+        ClientTickEvents.END_WORLD_TICK.register(world -> {
+            if (!Location.inDungeon() || !Phase.inP5() || !Floor7.showAllRelicTimes) return;
 
-            if (entity instanceof ArmorStandEntity armorStand) {
-                ItemStack helmet = armorStand.getEquippedStack(EquipmentSlot.HEAD);
-                Text text = helmet.getName();
-                if (text == null) return false;
-                String name = text.getString();
-                if (!name.contains("Relic")) return false;
+            Iterable<Entity> entities = world.getEntities();
+            if (allRelicsPlaced()) return;
 
-                double ax = armorStand.getX();
-                double az = armorStand.getZ();
+            for (Entity entity : entities) {
+                if (entity instanceof ArmorStandEntity armorStand) {
+                    if (!EntityUtil.isWearing(armorStand, EquipmentSlot.HEAD, "Relic")) return;
 
-                for (Relic relic : Relic.values()) {
-                    if (Misc.getDistance(relic.box.maxX, ax, relic.box.maxZ, az) < 1) {
-                        relic.placedTime = System.currentTimeMillis();
-                        break;
-                    }
-                }
+                    double ax = armorStand.getX();
+                    double az = armorStand.getZ();
 
-                if (allRelicsPlaced()) {
                     for (Relic relic : Relic.values()) {
-                        long time = relic.placedTime - phaseStartTime;
-                        Misc.addChatMessage(Text.literal(relic.name + " &aRelic placed in &e" + Constants.DECIMAL_FORMAT.format(time) + "s&a."));
+                        if (relic.placedTime != 0 && Misc.getDistance(relic.box.maxX, ax, relic.box.maxZ, az) < 1) {
+                            relic.placedTime = System.currentTimeMillis();
+                            break;
+                        }
                     }
                 }
             }
-            return false;
+
+            if (allRelicsPlaced()) {
+                for (Relic relic : Relic.values()) {
+                    long time = relic.placedTime - phaseStartTime;
+                    Misc.addChatMessage(Text.literal(relic.name + " &aRelic placed in &e" + Constants.DECIMAL_FORMAT.format(time) + "s&a."));
+                }
+            }
         });
 
+
         Events.ON_BLOCK_INTERACTION.register((result, itemStack) -> {
-            if (!Location.inDungeon() || !Phase.inP5() || pickedupRelic == null) return false;
+            if (!ignoreChecks) {
+                if ((!Location.inDungeon() || !Phase.inP5())) return false;
+            }
+
+            if (pickedupRelic == null) return false;
+
             BlockPos pos = result.getBlockPos();
 
-            Text name = itemStack.getCustomName();
-            if (name == null) return false;
-            if (!name.getString().contains("Relic")) return false;
+            if (!ItemUtil.itemHasName(itemStack, "Relic")) {
+                if (Floor7.blockIncorrectRelicPlace) {
+                    Debug.sendDebugMessage(Text.literal("Item: " + itemStack.getName()));
+                    Misc.addChatMessage(Text.literal("Blocked a weird click"));
+                    return true;
+                } else {
+                    return false;
+                }
+            }
 
             if (pos.getX() == pickedupRelic.box.minX && (pos.getY() == pickedupRelic.box.minY || pos.getY() == pickedupRelic.box.minY - 1) && pos.getZ() == pickedupRelic.box.minZ) {
                 if (Floor7.enableRelicPlaceTime) {
@@ -204,9 +219,12 @@ public class RelicTimer {
 
     }
 
-    public static void testRelicGUI() {
-        forceGUI = true;
+    private static void reset() {
         tick = Floor7.relicSpawnTicks;
+        pickedupRelic = null;
+        for (Relic relic : Relic.values()) {
+            relic.placedTime = 0;
+        }
     }
 
     private static boolean allRelicsPlaced() {
@@ -216,6 +234,27 @@ public class RelicTimer {
         return true;
     }
 
+    public static void testRelicGUI() {
+        forceGUI = true;
+        tick = Floor7.relicSpawnTicks;
+    }
+
+    public static boolean testSetRelic(String name) {
+        try {
+            pickedupRelic = RelicTimer.Relic.valueOf(name);
+            Misc.addChatMessage(Text.literal("Relic is now: " + name));
+            return true;
+        } catch (IllegalArgumentException e) {
+            return false;
+        }
+    }
+
+    public static void testIgnoreChecks() {
+        ignoreChecks = !ignoreChecks;
+        Misc.addChatMessage(Text.literal("Ignore relic checks: ").append(Misc.getStatusText(ignoreChecks)));
+
+    }
+
     public static boolean display() {
         return Floor7.enableRelicStartTimer && Location.inDungeon() && Phase.inP5() && tick > -1 && !Floor7.replaceWithProgressBar;
     }
@@ -242,8 +281,7 @@ public class RelicTimer {
                 if (i < tick) {
                     if (tick > 2) message.append("§a|");
                     else message.append("§c|");
-                }
-                else message.append("§7|");
+                } else message.append("§7|");
             }
             message.append("§8]");
         } else {
diff --git a/src/main/java/blade/addon/features/dungeon/f7/location/LocationNotifier.java b/src/main/java/blade/addon/features/dungeon/f7/location/LocationNotifier.java
index 608421e..6b205cf 100644
--- a/src/main/java/blade/addon/features/dungeon/f7/location/LocationNotifier.java
+++ b/src/main/java/blade/addon/features/dungeon/f7/location/LocationNotifier.java
@@ -1,15 +1,19 @@
 package blade.addon.features.dungeon.f7.location;
 
+import blade.addon.utils.Constants;
 import blade.addon.utils.Location;
-import blade.addon.utils.Misc;
 import blade.addon.utils.Scheduler;
+import blade.addon.utils.config.values.Dungeons;
 import blade.addon.utils.config.values.Floor7;
+import blade.addon.utils.data.EntityUtil;
+import blade.addon.utils.dungeon.DungeonClass;
 import blade.addon.utils.events.Events;
 import blade.addon.utils.rendering.RenderUtils;
 import config.practical.hud.HUDComponent;
 import net.fabricmc.fabric.api.client.event.lifecycle.v1.ClientTickEvents;
 import net.minecraft.client.MinecraftClient;
 import net.minecraft.client.gui.DrawContext;
+import net.minecraft.text.Style;
 import net.minecraft.text.Text;
 
 import java.util.regex.Matcher;
@@ -29,10 +33,10 @@ public class LocationNotifier {
             if (!matcher.find()) return false;
             String action = matcher.group(1);
 
-            if (Floor7.dontNotifiyForYourself && Misc.isClientPlayer(username)) return false;
-            String screenNotification = (username + " is " + action + message.substring(action.length()) + "!").replaceAll("§.", "");
-            if (PositionMessages.hasBeenSent(screenNotification)) return false;
-            startNotification(screenNotification);
+            if (Floor7.dontNotifiyForYourself && EntityUtil.isClientPlayer(username)) return false;
+            String location =message.substring(action.length()) .replaceAll("§.", "");
+            if (PositionMessages.hasBeenSent(location)) return false;
+            startNotification(username, " is " + action + location + "!");
 
             return false;
         });
@@ -40,8 +44,13 @@ public class LocationNotifier {
         ClientTickEvents.END_CLIENT_TICK.register(client -> ticks = Math.max(ticks - 1, 0));
     }
 
-    public static void startNotification(String message) {
-        notification = Text.literal("§6"+message);
+    public static void startNotification(String username, String message) {
+        int color = Constants.GOLD;
+        if (Dungeons.useClassColors) {
+            color = DungeonClass.getColor(username);
+        }
+
+        notification = Text.literal(username).withColor(color).append(Text.literal("§6" + message).setStyle(Style.EMPTY));
         ticks = Floor7.notificationDuration;
 
         for (int i = 0; i < Floor7.notificationRepetitions; i++) {
diff --git a/src/main/java/blade/addon/features/dungeon/f7/location/PositionMessages.java b/src/main/java/blade/addon/features/dungeon/f7/location/PositionMessages.java
index 8506f3a..9be2854 100644
--- a/src/main/java/blade/addon/features/dungeon/f7/location/PositionMessages.java
+++ b/src/main/java/blade/addon/features/dungeon/f7/location/PositionMessages.java
@@ -1,6 +1,6 @@
 package blade.addon.features.dungeon.f7.location;
 
-import blade.addon.utils.Debug;
+import blade.addon.utils.debug.Debug;
 import blade.addon.utils.Location;
 import blade.addon.utils.config.values.Floor7;
 import blade.addon.utils.dungeon.Phase;
diff --git a/src/main/java/blade/addon/features/dungeon/f7/terms/LeapNotification.java b/src/main/java/blade/addon/features/dungeon/f7/terms/LeapNotification.java
index c58d60f..a327cbe 100644
--- a/src/main/java/blade/addon/features/dungeon/f7/terms/LeapNotification.java
+++ b/src/main/java/blade/addon/features/dungeon/f7/terms/LeapNotification.java
@@ -2,7 +2,7 @@ package blade.addon.features.dungeon.f7.terms;
 
 import blade.addon.utils.Location;
 import blade.addon.utils.config.values.Floor7;
-import blade.addon.utils.detection.Detection;
+import blade.addon.utils.data.EntityUtil;
 import blade.addon.utils.dungeon.Phase;
 import blade.addon.utils.rendering.RenderUtils;
 import config.practical.hud.HUDComponent;
@@ -65,7 +65,7 @@ public class LeapNotification {
             List<Entity> entities = world.getOtherEntities(player, box);
             int currentCount = 0;
             for (Entity entity : entities) {
-                if (Detection.isARealPlayer(entity)) {
+                if (EntityUtil.isARealPlayer(entity)) {
                     currentCount++;
                 }
             }
diff --git a/src/main/java/blade/addon/features/dungeon/f7/terms/MelodyWarning.java b/src/main/java/blade/addon/features/dungeon/f7/terms/MelodyWarning.java
index bf71a90..3c11f84 100644
--- a/src/main/java/blade/addon/features/dungeon/f7/terms/MelodyWarning.java
+++ b/src/main/java/blade/addon/features/dungeon/f7/terms/MelodyWarning.java
@@ -1,8 +1,10 @@
 package blade.addon.features.dungeon.f7.terms;
 
+import blade.addon.utils.Constants;
 import blade.addon.utils.Location;
-import blade.addon.utils.Misc;
+import blade.addon.utils.config.values.Dungeons;
 import blade.addon.utils.config.values.Floor7;
+import blade.addon.utils.data.EntityUtil;
 import blade.addon.utils.dungeon.DungeonClass;
 import blade.addon.utils.dungeon.Phase;
 import blade.addon.utils.events.Events;
@@ -10,6 +12,8 @@ import blade.addon.utils.rendering.RenderUtils;
 import config.practical.hud.HUDComponent;
 import net.minecraft.client.MinecraftClient;
 import net.minecraft.client.gui.DrawContext;
+import net.minecraft.text.MutableText;
+import net.minecraft.text.Style;
 import net.minecraft.text.Text;
 
 import java.util.concurrent.CopyOnWriteArrayList;
@@ -39,7 +43,7 @@ public class MelodyWarning {
                     melodyStarted = true;
                     name = username;
                     furthestProgress = progress;
-                    ownUsername = Misc.isClientPlayer(username);
+                    ownUsername = EntityUtil.isClientPlayer(username);
                     if (!names.contains(username)) {
                         names.add(username);
                     }
@@ -84,7 +88,17 @@ public class MelodyWarning {
 
         DungeonClass dungeonClass = DungeonClass.getClass(name);
         int num = Math.min(furthestProgress / 25, 3);
-        Text text = Text.literal("§5§l" + (dungeonClass != null ? dungeonClass.name() : name != null? name: "Someone") + " §r§dhas melody! " + num + "/4");
+
+        int color = Constants.DARK_PURPLE;
+        if (Dungeons.useClassColors) {
+            color = DungeonClass.getColor(name);
+        }
+
+
+        MutableText nameText = Text.literal(dungeonClass != null ? dungeonClass.name() : name != null? name: "Someone").setStyle(Style.EMPTY.withColor(color).withBold(true));
+        MutableText infoText = Text.literal(" §r§dhas melody! " + num + "/4").setStyle(Style.EMPTY);
+
+        Text text = nameText.append(infoText);
 
         RenderUtils.drawCenteredText(context, MinecraftClient.getInstance().textRenderer, text, x, y, component.getWidth());
     }
diff --git a/src/main/java/blade/addon/features/dungeon/f7/terms/Pre4Notifier.java b/src/main/java/blade/addon/features/dungeon/f7/terms/Pre4Notifier.java
index 0e3a734..034e961 100644
--- a/src/main/java/blade/addon/features/dungeon/f7/terms/Pre4Notifier.java
+++ b/src/main/java/blade/addon/features/dungeon/f7/terms/Pre4Notifier.java
@@ -1,8 +1,8 @@
 package blade.addon.features.dungeon.f7.terms;
 
-import blade.addon.utils.Misc;
 import blade.addon.utils.Scheduler;
 import blade.addon.utils.config.values.Floor7;
+import blade.addon.utils.data.EntityUtil;
 import blade.addon.utils.dungeon.Phase;
 import blade.addon.utils.events.Events;
 import blade.addon.utils.rendering.RenderUtils;
@@ -25,7 +25,7 @@ public class Pre4Notifier {
         Events.ON_TERMINAL.register((name, action, objective, current, total) -> {
             if (!Floor7.notifyPre4Completion) return true;
 
-            if (objective.equals("device") && Misc.isClientPlayer(name) && atDev()) {
+            if (objective.equals("device") && EntityUtil.isClientPlayer(name) && atDev()) {
                 completedTime = System.currentTimeMillis();
                 showNotification = true;
                 Scheduler.scheduleSound(SoundEvents.BLOCK_NOTE_BLOCK_PLING.value(), 1, 1);
diff --git a/src/main/java/blade/addon/features/dungeon/f7/terms/TitleHider.java b/src/main/java/blade/addon/features/dungeon/f7/terms/TitleHider.java
index 48ad2ff..23d90cc 100644
--- a/src/main/java/blade/addon/features/dungeon/f7/terms/TitleHider.java
+++ b/src/main/java/blade/addon/features/dungeon/f7/terms/TitleHider.java
@@ -3,6 +3,7 @@ package blade.addon.features.dungeon.f7.terms;
 import blade.addon.utils.Location;
 import blade.addon.utils.Misc;
 import blade.addon.utils.config.values.Floor7;
+import blade.addon.utils.data.EntityUtil;
 import blade.addon.utils.dungeon.Phase;
 import blade.addon.utils.dungeon.Section;
 import net.minecraft.network.packet.s2c.play.SubtitleS2CPacket;
@@ -24,7 +25,7 @@ public class TitleHider {
         Matcher matcher = TERMINALS_DONE_PATTERN.matcher(string);
         if (matcher.find()) {
             String name = matcher.group(1);
-            if (Misc.isClientPlayer(name)) {
+            if (EntityUtil.isClientPlayer(name)) {
                 Misc.forceTitle(Text.empty(), packet.text());
             }
             //end of section msg is handled by the Section class currently
diff --git a/src/main/java/blade/addon/features/highlight/MobHighlight.java b/src/main/java/blade/addon/features/highlight/MobHighlight.java
index 86484b1..eec60bb 100644
--- a/src/main/java/blade/addon/features/highlight/MobHighlight.java
+++ b/src/main/java/blade/addon/features/highlight/MobHighlight.java
@@ -2,6 +2,7 @@ package blade.addon.features.highlight;
 
 import blade.addon.utils.Location;
 import blade.addon.utils.Misc;
+import blade.addon.utils.data.EntityUtil;
 import blade.addon.utils.dungeon.Phase;
 import blade.addon.utils.events.Events;
 import blade.addon.utils.rendering.RenderLayers;
@@ -315,7 +316,7 @@ public class MobHighlight {
     private static boolean isARealPlayer(Entity entity) {
         if (entity instanceof PlayerEntity player) {
 
-            if (Misc.isClientPlayer(player)) return true;
+            if (EntityUtil.isClientPlayer(player)) return true;
 
             ClientPlayNetworkHandler networkHandler = MinecraftClient.getInstance().getNetworkHandler();
             if (networkHandler == null) return false;
diff --git a/src/main/java/blade/addon/features/item/ProtectItem.java b/src/main/java/blade/addon/features/item/ProtectItem.java
index 37d97f5..594599f 100644
--- a/src/main/java/blade/addon/features/item/ProtectItem.java
+++ b/src/main/java/blade/addon/features/item/ProtectItem.java
@@ -1,7 +1,7 @@
 package blade.addon.features.item;
 
 import blade.addon.utils.Constants;
-import blade.addon.utils.Debug;
+import blade.addon.utils.debug.Debug;
 import blade.addon.utils.Location;
 import blade.addon.utils.Misc;
 import blade.addon.utils.config.FolderUtility;
@@ -113,7 +113,7 @@ public class ProtectItem {
             Slot slot = slots.get(49);
             ItemStack stack = slot.getStack();
             Text itemName = stack.getName();
-            if (itemName != null && itemName.getString().contains("Sell Item") || Misc.containsLore(stack, "Click to buyback!")) {
+            if (itemName != null && itemName.getString().contains("Sell Item") || ItemUtil.containsLore(stack, "Click to buyback!")) {
                 Misc.addChatMessage(Text.literal("Protected ").append(item.getName()).append(" From being sold"));
                 return true;
             }
diff --git a/src/main/java/blade/addon/features/notifications/Notification.java b/src/main/java/blade/addon/features/notifications/Notification.java
new file mode 100644
index 0000000..47c5f73
--- /dev/null
+++ b/src/main/java/blade/addon/features/notifications/Notification.java
@@ -0,0 +1,120 @@
+package blade.addon.features.notifications;
+
+import blade.addon.utils.Misc;
+import blade.addon.utils.debug.Debug;
+import config.practical.data.SoundData;
+import net.minecraft.sound.SoundEvents;
+import net.minecraft.text.Text;
+
+import java.util.regex.PatternSyntaxException;
+
+public class Notification {
+
+    private String matchString, notificationString, command;
+    private boolean useRegex, sendCommand;
+    private int ticks;
+    private final SoundData sound;
+
+    public Notification() {
+        this.matchString = "";
+        this.notificationString = "";
+        this.command = "";
+        this.useRegex = false;
+        this.sendCommand = false;
+        this.ticks = 20;
+        this.sound = new SoundData(SoundEvents.BLOCK_NOTE_BLOCK_PLING.value(), 1, 1);
+    }
+
+    public Notification(String matchString, String notificationString, String command, boolean useRegex, boolean sendCommand, int ticks, SoundData sound) {
+        this.matchString = matchString;
+        this.notificationString = notificationString;
+        this.command = command;
+        this.useRegex = useRegex;
+        this.sendCommand = sendCommand;
+        this.ticks = ticks;
+        this.sound = sound;
+    }
+
+    public String getMatchString() {
+        return matchString;
+    }
+
+    public String getNotificationString() {
+        return notificationString;
+    }
+
+    public int getTicks() {
+        return ticks;
+    }
+
+    public String getCommand() {
+        return command;
+    }
+
+    public boolean sendCommand() {
+        return sendCommand;
+    }
+
+    public SoundData getSound() {
+        return sound;
+    }
+
+    public boolean useRegex() {
+        return useRegex;
+    }
+
+
+    public void setMatchString(String matchString) {
+        this.matchString = matchString;
+    }
+
+    public void setNotificationString(String notificationString) {
+        this.notificationString = notificationString;
+    }
+
+    public void setCommand(String command) {
+        this.command = command;
+    }
+
+    public void setTicks(int ticks) {
+        this.ticks = ticks;
+    }
+
+    public void setSendCommand(boolean sendCommand) {
+        this.sendCommand = sendCommand;
+    }
+
+    public void setUseRegex(boolean useRegex) {
+        this.useRegex = useRegex;
+    }
+
+    public void testMessage(String message) {
+        if (useRegex) {
+            try {
+                if (!message.matches(matchString)) return;
+            } catch (PatternSyntaxException e) {
+                return;
+            }
+        } else if (!message.equals(matchString)) return;
+
+        Debug.sendDebugMessage(Text.literal("Debug: " + notificationString));
+        Notifications.setNotification(this);
+        Misc.sendSound(sound);
+        if (sendCommand) {
+            Misc.executeCommand(command);
+        }
+    }
+
+    @Override
+    public String toString() {
+        return "Notification{" +
+                "matchString='" + matchString + '\'' +
+                ", notificationString='" + notificationString + '\'' +
+                ", command='" + command + '\'' +
+                ", useRegex=" + useRegex +
+                ", sendCommand=" + sendCommand +
+                ", ticks=" + ticks +
+                ", sound=" + sound +
+                '}';
+    }
+}
diff --git a/src/main/java/blade/addon/features/notifications/NotificationEditScreen.java b/src/main/java/blade/addon/features/notifications/NotificationEditScreen.java
new file mode 100644
index 0000000..94a34e3
--- /dev/null
+++ b/src/main/java/blade/addon/features/notifications/NotificationEditScreen.java
@@ -0,0 +1,55 @@
+package blade.addon.features.notifications;
+
+import config.practical.ConfigScroll;
+import config.practical.utilities.Constants;
+import config.practical.widgets.ConfigBool;
+import config.practical.widgets.ConfigString;
+import config.practical.widgets.sliders.ConfigInt;
+import config.practical.widgets.sound.ConfigSound;
+import net.minecraft.client.MinecraftClient;
+import net.minecraft.client.gui.screen.Screen;
+import net.minecraft.client.util.Window;
+import net.minecraft.text.Text;
+
+public class NotificationEditScreen extends Screen {
+
+    private final Screen parent;
+    private final ConfigScroll scroll;
+    private final Notification notification;
+
+
+    protected NotificationEditScreen(Notification notification) {
+        super(Text.empty());
+
+        MinecraftClient client = MinecraftClient.getInstance();
+        parent = client.currentScreen;
+        Window window = client.getWindow();
+        scroll = new ConfigScroll(0,  30, window.getScaledWidth(), window.getScaledHeight(), Constants.WIDGET_WIDTH);
+        this.notification = notification;
+    }
+
+    private void initScrollWidgets() {
+        scroll.children().clear();
+        scroll.add(new ConfigString(Text.literal("Trigger on message"), notification::getMatchString, notification::setMatchString, false));
+        scroll.add(new ConfigString(Text.literal("Notification message"), notification::getNotificationString, notification::setNotificationString));
+        scroll.add(new ConfigBool(Text.literal("Use regex"), notification::useRegex, notification::setUseRegex));
+        scroll.add(new ConfigInt(Text.literal("Duration in ticks"), notification::getTicks, notification::setTicks, 1, 10, 40));
+        scroll.add(new ConfigSound(Text.literal("Sound"), notification.getSound(), 2, 2, true));
+        scroll.add(new ConfigBool(Text.literal("Send command"), notification::sendCommand, notification::setSendCommand));
+        scroll.add(new ConfigString(Text.literal("Command"), notification::getCommand, notification::setCommand));
+        scroll.update();
+    }
+
+    @Override
+    protected void init() {
+        super.init();
+        this.addDrawableChild(scroll);
+        initScrollWidgets();
+    }
+
+    @Override
+    public void close() {
+        assert this.client != null;
+        this.client.setScreen(this.parent);
+    }
+}
diff --git a/src/main/java/blade/addon/features/notifications/NotificationEntry.java b/src/main/java/blade/addon/features/notifications/NotificationEntry.java
new file mode 100644
index 0000000..598b369
--- /dev/null
+++ b/src/main/java/blade/addon/features/notifications/NotificationEntry.java
@@ -0,0 +1,84 @@
+package blade.addon.features.notifications;
+
+import config.practical.utilities.Constants;
+import config.practical.utilities.DrawHelper;
+import net.minecraft.client.MinecraftClient;
+import net.minecraft.client.font.TextRenderer;
+import net.minecraft.client.gui.Click;
+import net.minecraft.client.gui.DrawContext;
+import net.minecraft.client.gui.screen.narration.NarrationMessageBuilder;
+import net.minecraft.client.gui.widget.ClickableWidget;
+import net.minecraft.text.Text;
+
+class NotificationEntry extends ClickableWidget {
+
+    private static final int PADDING = 5;
+    private static final int TEXT_SPACE = 100;
+    private static final int MAX_TEXT_WIDTH = 75;
+    private static final int BUTTON_WIDTH = 60;
+
+
+    private final NotificationList parent ;
+    private final Notification notification;
+
+    public NotificationEntry(NotificationList parent, Notification notification) {
+        super(0, 0, Constants.WIDGET_WIDTH, 30, Text.empty());
+        this.parent = parent;
+        this.notification = notification;
+    }
+
+    @Override
+    protected void renderWidget(DrawContext context, int mouseX, int mouseY, float deltaTicks) {
+        int x = getX();
+        int y = getY();
+        int width = getWidth();
+        int height = getHeight();
+        DrawHelper.drawBackground(context, x, y, width, height);
+
+        MinecraftClient mc = MinecraftClient.getInstance();
+        TextRenderer textRenderer = mc.textRenderer;
+
+        drawText(context, textRenderer, notification.getMatchString(), x + PADDING, y);
+        drawText(context, textRenderer, notification.getNotificationString(), x + PADDING * 2 + TEXT_SPACE, y);
+
+        int buttonWidth = 60;
+        String remove = "Remove";
+        textRenderer.trimToWidth(remove, TEXT_SPACE - 2);
+        DrawHelper.drawBackground(context, x + width - buttonWidth - PADDING, y + 4, buttonWidth, height - 8);
+        context.drawText(textRenderer, remove, x + width - buttonWidth, y + (30 - Constants.TEXT_HEIGHT) / 2, 0xffffffff, true);
+    }
+
+    private void drawText(DrawContext context, TextRenderer textRenderer, String string, int x, int y) {
+
+        Text text;
+        if (string.isEmpty()) {
+            text = Text.literal("Empty");
+        } else {
+            text = Text.literal(textRenderer.trimToWidth(string, MAX_TEXT_WIDTH - 2));
+        }
+
+        int textY = (30 - Constants.TEXT_HEIGHT) / 2 + y;
+
+        context.enableScissor(x, y, x + MAX_TEXT_WIDTH - 1, y + height);
+        context.drawText(textRenderer, text, x + 1, textY, 0xffffffff, true);
+        context.disableScissor();
+    }
+
+    @Override
+    public void onClick(Click click, boolean doubled) {
+        double x = click.x();
+        double y = click.y();
+
+        if (x >= getX() + width - BUTTON_WIDTH - PADDING && x <= getX() + width + BUTTON_WIDTH && y >= getY() + 4 && y <= getY() + height - 4) {
+            parent.removeNotification(notification);
+            return;
+        }
+
+        MinecraftClient.getInstance().setScreen(new NotificationEditScreen(notification));
+    }
+
+    @Override
+    protected void appendClickableNarrations(NarrationMessageBuilder builder) {
+
+    }
+}
diff --git a/src/main/java/blade/addon/features/notifications/NotificationList.java b/src/main/java/blade/addon/features/notifications/NotificationList.java
new file mode 100644
index 0000000..6721013
--- /dev/null
+++ b/src/main/java/blade/addon/features/notifications/NotificationList.java
@@ -0,0 +1,89 @@
+package blade.addon.features.notifications;
+
+import config.practical.ConfigScroll;
+import config.practical.utilities.Constants;
+import net.minecraft.client.MinecraftClient;
+import net.minecraft.client.gui.DrawContext;
+import net.minecraft.client.gui.screen.Screen;
+import net.minecraft.client.gui.widget.ButtonWidget;
+import net.minecraft.client.util.Window;
+import net.minecraft.text.Text;
+import org.joml.Matrix3x2fStack;
+
+import java.util.List;
+
+public class NotificationList extends Screen {
+
+    private static final int TITLE_COLOR = 0xffffffff;
+    private static final float TITLE_SCALAR = 1.5f;
+    private static final int TITLE_Y_OFFSET = 20;
+
+    private static final int BUTTON_HEIGHT = 30;
+
+    private final Screen parent;
+    private final ConfigScroll scroll;
+    private final ButtonWidget addNotification;
+
+    public NotificationList() {
+        super(Text.literal("Notifications"));
+
+        MinecraftClient client = MinecraftClient.getInstance();
+        parent = client.currentScreen;
+        Window window = client.getWindow();
+
+        scroll = new ConfigScroll(0,  BUTTON_HEIGHT + TITLE_Y_OFFSET + 16, window.getScaledWidth(), window.getScaledHeight() - BUTTON_HEIGHT, Constants.WIDGET_WIDTH);
+        addNotification = ButtonWidget.builder(Text.literal("Add notification"), this::addNotification).position((window.getScaledWidth() - Constants.WIDGET_WIDTH) / 2,  TITLE_Y_OFFSET + 16).width(100).build();
+    }
+
+    @Override
+    protected void init() {
+        super.init();
+        this.addDrawableChild(scroll);
+        this.addDrawableChild(addNotification);
+        updateList();
+    }
+
+    @Override
+    public void render(DrawContext context, int mouseX, int mouseY, float deltaTicks) {
+        super.render(context, mouseX, mouseY, deltaTicks);
+
+        float centerX = (MinecraftClient.getInstance().getWindow().getScaledWidth() - (this.textRenderer.getWidth(this.title) * TITLE_SCALAR)) / 2;
+        Matrix3x2fStack stack = context.getMatrices();
+        stack.pushMatrix();
+        stack.translate(centerX, TITLE_Y_OFFSET);
+        stack.scale(TITLE_SCALAR, TITLE_SCALAR);
+        context.drawText(this.textRenderer, this.title, 0, 0, TITLE_COLOR, true);
+        stack.popMatrix();
+    }
+
+    @Override
+    public void close() {
+        assert this.client != null;
+        Notifications.save();
+        this.client.setScreen(this.parent);
+    }
+
+    private void updateList() {
+        scroll.children().clear();
+
+        List<Notification> notifications = Notifications.getNotifications();
+        for (Notification notification : notifications) {
+            NotificationEntry entry = new NotificationEntry(this, notification);
+            scroll.add(entry);
+        }
+
+        scroll.update();
+        scroll.setScrollY(0);
+    }
+
+    private void addNotification(ButtonWidget buttonWidget) {
+        Notification notification = new Notification();
+        Notifications.addNotification(notification);
+        updateList();
+    }
+
+    public void removeNotification(Notification notification) {
+        Notifications.removeNotification(notification);
+        updateList();
+    }
+}
diff --git a/src/main/java/blade/addon/features/notifications/Notifications.java b/src/main/java/blade/addon/features/notifications/Notifications.java
new file mode 100644
index 0000000..8611e83
--- /dev/null
+++ b/src/main/java/blade/addon/features/notifications/Notifications.java
@@ -0,0 +1,160 @@
+package blade.addon.features.notifications;
+
+import blade.addon.utils.config.FolderUtility;
+import blade.addon.utils.events.Events;
+import blade.addon.utils.rendering.RenderUtils;
+import com.google.gson.Gson;
+import com.google.gson.JsonArray;
+import com.google.gson.JsonElement;
+import com.google.gson.JsonObject;
+import config.practical.data.SoundData;
+import config.practical.hud.HUDComponent;
+import config.practical.manager.ConfigValue;
+import net.fabricmc.fabric.api.client.event.lifecycle.v1.ClientTickEvents;
+import net.minecraft.client.gui.DrawContext;
+import net.minecraft.text.Text;
+import net.minecraft.util.Identifier;
+
+import java.io.FileWriter;
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Paths;
+import java.util.List;
+import java.util.concurrent.CopyOnWriteArrayList;
+
+public class Notifications {
+
+    private static final String filePath = FolderUtility.CONFIG_PATH + FolderUtility.NOTIFICATIONS_NAME;
+    private static final String WAYPOINTS_NAME = "waypoints";
+
+    @ConfigValue
+    public static CopyOnWriteArrayList<Notification> notifications;
+
+    private static double tick = 0;
+    private static String message;
+
+    public static void init() {
+        notifications = new CopyOnWriteArrayList<>();
+        load();
+        System.out.println("Noti: " + notifications);
+        Events.ON_GAME_MESSAGE.register(text -> {
+            String message = text.getString();
+            for (Notification notification : notifications) {
+                notification.testMessage(message);
+            }
+
+            return false;
+        });
+
+        ClientTickEvents.END_CLIENT_TICK.register(event -> {
+            tick = Math.max(tick - 1, 0);
+        });
+    }
+
+    public static void addNotification(Notification notification) {
+        if (notifications == null) return;
+        notifications.add(notification);
+    }
+
+    public static void removeNotification(Notification notification) {
+        if (notifications == null) return;
+        notifications.remove(notification);
+    }
+
+    public static List<Notification> getNotifications() {
+        return notifications;
+    }
+
+    public static boolean display() {
+        return tick > 0;
+    }
+
+    public static void render(HUDComponent component, DrawContext context) {
+        Text text = Text.literal(message != null ? message : "Some notification");
+        RenderUtils.drawCenteredText(context, component, text);
+    }
+
+    public static void setNotification(Notification notification) {
+        if (notifications == null) return;
+        tick = notification.getTicks();
+        message = notification.getNotificationString();
+    }
+
+    public static void save() {
+
+        JsonObject obj = new JsonObject();
+        Gson gson = new Gson();
+
+        obj.add(WAYPOINTS_NAME, gson.toJsonTree(notifications));
+
+        JsonElement tree = gson.toJsonTree(obj);
+        try (FileWriter writer = new FileWriter(filePath)) {
+            writer.write(tree.toString());
+        } catch (IOException ignored) {
+
+        }
+    }
+
+    public static void load() {
+        String jsonContent;
+        try {
+            jsonContent = new String(Files.readAllBytes(Paths.get(filePath)));
+        } catch (IOException ignored) {
+            return;
+        }
+        Gson gson = new Gson();
+        JsonObject object = gson.fromJson(jsonContent, JsonObject.class);
+        JsonElement element = object.get(WAYPOINTS_NAME);
+
+        if (!element.isJsonArray()) return;
+        JsonArray array = element.getAsJsonArray();
+
+        for (JsonElement notificationElement : array) {
+            JsonObject notification = notificationElement.getAsJsonObject();
+
+            try {
+
+                if (!notification.has("matchString")) continue;
+                String matchString = notification.get("matchString").getAsString();
+
+                if (!notification.has("notificationString")) continue;
+                String notificationString = notification.get("notificationString").getAsString();
+
+                if (!notification.has("command")) continue;
+                String command = notification.get("command").getAsString();
+
+                if (!notification.has("useRegex")) continue;
+                boolean useRegex = notification.get("useRegex").getAsBoolean();
+
+                if (!notification.has("sendCommand")) continue;
+                boolean sendCommand = notification.get("sendCommand").getAsBoolean();
+
+                if (!notification.has("ticks")) continue;
+                int ticks = notification.get("ticks").getAsInt();
+
+                if (!notification.has("sound")) continue;
+                SoundData sound = loadSoundData(notification.get("sound"));
+                if  (sound == null) continue;
+
+                notifications.add(new Notification(matchString, notificationString, command, useRegex, sendCommand, ticks, sound));
+            } catch (NumberFormatException e) {
+                System.out.println("msg: " + e.getMessage());
+            }
+        }
+    }
+
+    public static SoundData loadSoundData(JsonElement jsonElement) {
+        JsonObject soundData = jsonElement.getAsJsonObject();
+
+        if (!soundData.has("sound")) return null;
+        String sound = soundData.get("sound").getAsString();
+
+        if (!soundData.has("volume")) return null;
+        float volume = soundData.get("volume").getAsFloat();
+
+        if (!soundData.has("pitch")) return null;
+        float pitch = soundData.get("pitch").getAsFloat();
+
+        return new SoundData(Identifier.of(sound), volume, pitch);
+    }
+}
diff --git a/src/main/java/blade/addon/features/other/DianaNotifier.java b/src/main/java/blade/addon/features/other/DianaNotifier.java
index 7111610..ed1f15a 100644
--- a/src/main/java/blade/addon/features/other/DianaNotifier.java
+++ b/src/main/java/blade/addon/features/other/DianaNotifier.java
@@ -1,6 +1,6 @@
 package blade.addon.features.other;
 
-import blade.addon.utils.Debug;
+import blade.addon.utils.debug.Debug;
 import blade.addon.utils.Location;
 import blade.addon.utils.Misc;
 import blade.addon.utils.Scheduler;
diff --git a/src/main/java/blade/addon/features/other/KickedTimer.java b/src/main/java/blade/addon/features/other/KickedTimer.java
index bc79228..d6b787e 100644
--- a/src/main/java/blade/addon/features/other/KickedTimer.java
+++ b/src/main/java/blade/addon/features/other/KickedTimer.java
@@ -19,7 +19,7 @@ public class KickedTimer {
             String string = text.getString();
             if (string == null) return false;
 
-            if (string.contains("You were kicked while joining that server!")) {
+            if (string.equals("You were kicked while joining that server!")) {
                 kickedTime = System.currentTimeMillis();
                 isKicked = true;
             }
diff --git a/src/main/java/blade/addon/features/other/RagDisplay.java b/src/main/java/blade/addon/features/other/RagDisplay.java
index 486b344..94074a7 100644
--- a/src/main/java/blade/addon/features/other/RagDisplay.java
+++ b/src/main/java/blade/addon/features/other/RagDisplay.java
@@ -11,17 +11,20 @@ import net.minecraft.client.gui.DrawContext;
 import net.minecraft.entity.passive.WolfSoundVariants;
 import net.minecraft.sound.SoundEvent;
 import net.minecraft.sound.SoundEvents;
+import net.minecraft.util.Identifier;
 
 public class RagDisplay {
 
     private static final SoundEvent SOUND = SoundEvents.WOLF_SOUNDS.get(WolfSoundVariants.Type.CLASSIC).deathSound().value();
+    private static final SoundEvent OLD_RAG_SOUND = SoundEvent.of(Identifier.of(Constants.NAMESPACE, "wolf-howl"));
     private static final int TOTAL_TICKS = 200;
 
     private static int tick = 0;
 
     public static void init() {
         Events.ON_SOUND.register((soundEvent, volume, pitch) -> {
-            if (!ExtraOptions.enableRagaxeDisplay && !ExtraOptions.useCustomRagSound) return false;
+            if (!ExtraOptions.enableRagaxeDisplay && !ExtraOptions.useCustomRagSound && !ExtraOptions.useOldRagSound)
+                return false;
             if (soundEvent != SOUND) return false;
             if (volume != 1 && pitch != 1.4920635223388672) return false;
             if (!ItemUtil.isHolding("Ragnarock")) return false;
@@ -30,7 +33,10 @@ public class RagDisplay {
                 tick = TOTAL_TICKS;
             }
 
-            if (ExtraOptions.useCustomRagSound) {
+            if (ExtraOptions.useOldRagSound) {
+                Scheduler.scheduleSound(OLD_RAG_SOUND, 1, 1.4920635223388672f);
+                return true;
+            } else if (ExtraOptions.useCustomRagSound) {
                 Scheduler.scheduleSound(ExtraOptions.ragSound);
                 return true;
             }
diff --git a/src/main/java/blade/addon/features/other/SelectedPet.java b/src/main/java/blade/addon/features/other/SelectedPet.java
index 3531e2d..184c51a 100644
--- a/src/main/java/blade/addon/features/other/SelectedPet.java
+++ b/src/main/java/blade/addon/features/other/SelectedPet.java
@@ -1,7 +1,7 @@
 package blade.addon.features.other;
 
 import blade.addon.utils.Constants;
-import blade.addon.utils.Debug;
+import blade.addon.utils.debug.Debug;
 import blade.addon.utils.Scheduler;
 import blade.addon.utils.config.values.ExtraOptions;
 import blade.addon.utils.events.Events;
diff --git a/src/main/java/blade/addon/mixin/ChatScreenMixin.java b/src/main/java/blade/addon/mixin/ChatScreenMixin.java
index 701e788..4b01f6e 100644
--- a/src/main/java/blade/addon/mixin/ChatScreenMixin.java
+++ b/src/main/java/blade/addon/mixin/ChatScreenMixin.java
@@ -1,13 +1,12 @@
 package blade.addon.mixin;
 
-import blade.addon.utils.Misc;
 import blade.addon.utils.config.values.ExtraOptions;
+import blade.addon.utils.data.TextUtil;
 import net.minecraft.client.MinecraftClient;
 import net.minecraft.client.gui.Click;
 import net.minecraft.client.gui.hud.ChatHudLine;
 import net.minecraft.client.gui.screen.ChatScreen;
 import net.minecraft.client.gui.screen.Screen;
-import net.minecraft.text.OrderedText;
 import net.minecraft.text.Text;
 import org.lwjgl.glfw.GLFW;
 import org.spongepowered.asm.mixin.Mixin;
@@ -43,9 +42,9 @@ public class ChatScreenMixin extends Screen {
             if (visibleMessages == null || index < 0 || index >= visibleMessages.size()) return;
 
             ChatHudLine.Visible msg = visibleMessages.get(index);
-            string = orderedTextToString(msg.content());
+            string = TextUtil.orderedTextToString(msg.content());
         } else {
-            string = Misc.copyChat(hudInvoker, x, y);
+            string = copyChat(hudInvoker, x, y);
         }
 
         if (string == null) return;
@@ -62,14 +61,38 @@ public class ChatScreenMixin extends Screen {
     }
 
     @Unique
-    private static String orderedTextToString(OrderedText text) {
-        StringBuilder builder = new StringBuilder();
+    private static String copyChat(ChatHudInvoker hudInvoker, double x, double y) {
+        List<ChatHudLine.Visible> messages = hudInvoker.getVisibleMessages();
+        int endIndex = hudInvoker.getLineIndex(x, y);
+
+        if (messages == null || endIndex < 0 || endIndex >= messages.size()) return null;
 
-        text.accept((index, style, codePoint) -> {
-            builder.appendCodePoint(codePoint);
-            return true;
-        });
+        int startIndex = endIndex;
+
+        //find start of msg
+        for (int i = endIndex; i >= 0; i--) {
+            ChatHudLine.Visible chatHudLine = messages.get(i);
+            if (chatHudLine.endOfEntry()) {
+                startIndex = i;
+                break;
+            }
+        }
 
+        if (!messages.get(endIndex).endOfEntry()) {
+            //find end of msg
+            for (int i = endIndex + 1; i < messages.size(); i++) {
+                ChatHudLine.Visible chatHudLine = messages.get(i);
+                if (chatHudLine.endOfEntry()) {
+                    endIndex = i - 1;
+                    break;
+                }
+            }
+        }
+        StringBuilder builder = new StringBuilder();
+        for (int i = endIndex; i >= startIndex; i--) {
+            ChatHudLine.Visible chatHudLine = messages.get(i);
+            TextUtil.acceptOrderedText(builder, chatHudLine.content());
+        }
         return builder.toString();
     }
 }
diff --git a/src/main/java/blade/addon/mixin/ClientPlayNetworkHandlerMixin.java b/src/main/java/blade/addon/mixin/ClientPlayNetworkHandlerMixin.java
index 5afad5d..ddd53a6 100644
--- a/src/main/java/blade/addon/mixin/ClientPlayNetworkHandlerMixin.java
+++ b/src/main/java/blade/addon/mixin/ClientPlayNetworkHandlerMixin.java
@@ -1,10 +1,11 @@
 package blade.addon.mixin;
 
 import blade.addon.features.dungeon.f7.terms.TitleHider;
-import blade.addon.utils.Debug;
+import blade.addon.utils.data.EntityUtil;
+import blade.addon.utils.debug.Debug;
 import blade.addon.utils.Misc;
 import blade.addon.utils.config.values.ExtraOptions;
-import blade.addon.utils.detection.PlayerDataHolder;
+import blade.addon.utils.interfaces.PlayerDataHolder;
 import blade.addon.utils.events.Events;
 import com.llamalad7.mixinextras.injector.ModifyExpressionValue;
 import com.llamalad7.mixinextras.sugar.Local;
@@ -16,6 +17,7 @@ import net.minecraft.entity.Entity;
 import net.minecraft.entity.player.PlayerEntity;
 import net.minecraft.network.packet.s2c.play.EntitySpawnS2CPacket;
 import net.minecraft.network.packet.s2c.play.EntityTrackerUpdateS2CPacket;
+import net.minecraft.network.packet.s2c.play.PlaySoundFromEntityS2CPacket;
 import net.minecraft.network.packet.s2c.play.PlaySoundS2CPacket;
 import net.minecraft.network.packet.s2c.play.PlayerListS2CPacket;
 import net.minecraft.network.packet.s2c.play.SubtitleS2CPacket;
@@ -68,12 +70,31 @@ public class ClientPlayNetworkHandlerMixin {
         float pitch = packet.getPitch();
         SoundEvent event = packet.getSound().value();
 
-        if (pitch == 0.0 && volume == 8.0 && event == SoundEvents.ENTITY_ENDERMAN_TELEPORT && ExtraOptions.disableAbilityCooldownSound) {
+        if (ExtraOptions.disableAbilityCooldownSound && pitch == 0.0 && volume == 8.0 && event == SoundEvents.ENTITY_ENDERMAN_TELEPORT) {
             ci.cancel();
         }
 
+        if (ExtraOptions.disableBonzoSound && volume == 1.0 && event == SoundEvents.ENTITY_GHAST_WARN) {
+            ci.cancel();
+        }
+
+        if (Debug.sendSound) {
+            Misc.addChatMessage(Text.literal("Sound: " + event.id() + " Volume: " + volume + " Pitch: " + pitch));
+        }
+
+        if (Events.ON_SOUND.invoke(soundEvent -> soundEvent.onSound(event, volume, pitch))) {
+            ci.cancel();
+        }
+    }
+
+    @Inject(method = "onPlaySoundFromEntity", at = @At(value = "HEAD"), cancellable = true)
+    private void onLocationSound(PlaySoundFromEntityS2CPacket packet, CallbackInfo ci) {
+        float volume = packet.getVolume();
+        float pitch = packet.getPitch();
+        SoundEvent event = packet.getSound().value();
+
         if (Debug.sendSound) {
-            Misc.addChatMessage(Text.literal("Sound: " + event.id()));
+            Misc.addChatMessage(Text.literal("Sound: " + event.id() + " Volume: " + volume + " Pitch: " + pitch));
         }
 
         if (Events.ON_SOUND.invoke(soundEvent -> soundEvent.onSound(event, volume, pitch))) {
@@ -87,7 +108,6 @@ public class ClientPlayNetworkHandlerMixin {
     }
 
 
-
     @Inject(method = "onEntitySpawn", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/network/ClientPlayNetworkHandler;playSpawnSound(Lnet/minecraft/entity/Entity;)V"))
     public void onEntitySpawn(EntitySpawnS2CPacket packet, CallbackInfo ci, @Local Entity entity) {
         if (entity instanceof PlayerEntity player) {
@@ -102,7 +122,7 @@ public class ClientPlayNetworkHandlerMixin {
     private static boolean isARealPlayer(Entity entity) {
         if (entity instanceof PlayerEntity player) {
 
-            if (Misc.isClientPlayer(player)) return true;
+            if (EntityUtil.isClientPlayer(player)) return true;
 
             ClientPlayNetworkHandler networkHandler = MinecraftClient.getInstance().getNetworkHandler();
             if (networkHandler == null) return false;
diff --git a/src/main/java/blade/addon/mixin/EntityRendererMixin.java b/src/main/java/blade/addon/mixin/EntityRendererMixin.java
index 83e2dc2..436a4db 100644
--- a/src/main/java/blade/addon/mixin/EntityRendererMixin.java
+++ b/src/main/java/blade/addon/mixin/EntityRendererMixin.java
@@ -1,8 +1,8 @@
 package blade.addon.mixin;
 
-import blade.addon.utils.Misc;
 import blade.addon.utils.config.values.Dungeons;
 import blade.addon.utils.config.values.ExtraOptions;
+import blade.addon.utils.data.EntityUtil;
 import net.minecraft.client.render.command.OrderedRenderCommandQueue;
 import net.minecraft.client.render.entity.EntityRenderer;
 import net.minecraft.client.render.entity.state.EntityRenderState;
@@ -32,7 +32,7 @@ public class EntityRendererMixin<T extends Entity, S extends EntityRenderState>
     public void hideFire(T entity, S state, float tickProgress, CallbackInfo ci) {
 
         if (entity instanceof PlayerEntity player && ExtraOptions.hideFireInf5) {
-            if (Misc.isClientPlayer(player)) {
+            if (EntityUtil.isClientPlayer(player)) {
                 state.onFire = false;
             }
         }
diff --git a/src/main/java/blade/addon/mixin/HeldItemRendererMixin.java b/src/main/java/blade/addon/mixin/HeldItemRendererMixin.java
index edd7c88..c75ce2e 100644
--- a/src/main/java/blade/addon/mixin/HeldItemRendererMixin.java
+++ b/src/main/java/blade/addon/mixin/HeldItemRendererMixin.java
@@ -2,7 +2,6 @@ package blade.addon.mixin;
 
 import blade.addon.features.item.DropAnimation;
 import com.llamalad7.mixinextras.sugar.Local;
-import net.minecraft.client.network.AbstractClientPlayerEntity;
 import net.minecraft.client.network.ClientPlayerEntity;
 import net.minecraft.client.render.item.HeldItemRenderer;
 import net.minecraft.client.util.math.MatrixStack;
@@ -13,7 +12,6 @@ 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.ModifyVariable;
 import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
 
 @Mixin(HeldItemRenderer.cl
…… diff 过长已截断 ……
Update README.mdBlade · 2026-01-04 · 4f276ad
查看变更
README.md | 5 ++++-
 1 file changed, 4 insertions(+), 1 deletion(-)
diff --git a/README.md b/README.md
index cba1b74..fc622ed 100644
--- a/README.md
+++ b/README.md
@@ -3,5 +3,8 @@
 A dungeons mod made to port dungeons features to the newer Minecraft versions. Huge thanks to Ohful for letting me port
 his amazing mods like Valley addons.
 
+# Current Version
+1.21.10
+
 # Dependencies
-- Hypixel Mod API https://modrinth.com/mod/hypixel-mod-api
\ No newline at end of file
+- Hypixel Mod API https://modrinth.com/mod/hypixel-mod-api
- added copy chat msg - did some refactoring - hopefully fixed title hider in terms - changed how player missing notifier worksGabriel Ellnebrand · 2026-01-04 · d6752e7
查看变更
src/main/java/blade/addon/Bladeaddons.java                          |  3 +++
 src/main/java/blade/addon/features/dungeon/KeyNotifier.java         |  3 ++-
 src/main/java/blade/addon/features/dungeon/RunStartValidator.java   | 10 +++++++++-
 src/main/java/blade/addon/features/dungeon/f7/terms/TitleHider.java | 10 +---------
 src/main/java/blade/addon/features/other/SelectedPet.java           | 13 +++++++++----
 src/main/java/blade/addon/mixin/ChatHudInvoker.java                 | 25 +++++++++++++++++++++++++
 src/main/java/blade/addon/mixin/ChatScreenMixin.java                | 75 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 src/main/java/blade/addon/mixin/EntityRenderManagerMixin.java       |  5 +++--
 src/main/java/blade/addon/utils/Location.java                       |  1 -
 src/main/java/blade/addon/utils/Misc.java                           | 45 ++++++++++++++++++++++++++++++++++++++++++++-
 src/main/java/blade/addon/utils/config/Config.java                  | 14 ++++++++++++--
 src/main/java/blade/addon/utils/config/values/Dungeons.java         |  3 ---
 src/main/java/blade/addon/utils/config/values/ExtraOptions.java     | 18 ++++++++++++++++++
 src/main/java/blade/addon/utils/data/PartyUtil.java                 | 53 +++++++++++++++++++++++++++++++++++++++++++++++++++++
 src/main/java/blade/addon/utils/dungeon/Phase.java                  | 11 +++++++++--
 src/main/java/blade/addon/utils/dungeon/Section.java                |  5 +++++
 src/main/resources/blade-addons.mixins.json                         |  2 ++
 17 files changed, 270 insertions(+), 26 deletions(-)
diff --git a/src/main/java/blade/addon/Bladeaddons.java b/src/main/java/blade/addon/Bladeaddons.java
index 00a237f..faa90b1 100644
--- a/src/main/java/blade/addon/Bladeaddons.java
+++ b/src/main/java/blade/addon/Bladeaddons.java
@@ -45,6 +45,7 @@ import blade.addon.utils.config.Config;
 import blade.addon.utils.config.FolderUtility;
 import blade.addon.utils.config.components.Components;
 import blade.addon.utils.config.values.Buttons;
+import blade.addon.utils.data.PartyUtil;
 import blade.addon.utils.dungeon.DungeonClass;
 import blade.addon.utils.dungeon.Phase;
 import blade.addon.utils.dungeon.Section;
@@ -66,6 +67,8 @@ public class Bladeaddons implements ModInitializer {
         Phase.init();
         Section.init();
         DungeonClass.init();
+        PartyUtil.init();
+
         StormTickTimer.init();
         GoldorTickTimer.init();
         TermStartTimer.init();
diff --git a/src/main/java/blade/addon/features/dungeon/KeyNotifier.java b/src/main/java/blade/addon/features/dungeon/KeyNotifier.java
index edb4930..ed5cddb 100644
--- a/src/main/java/blade/addon/features/dungeon/KeyNotifier.java
+++ b/src/main/java/blade/addon/features/dungeon/KeyNotifier.java
@@ -5,6 +5,7 @@ import blade.addon.utils.Scheduler;
 import blade.addon.utils.config.values.Dungeons;
 import blade.addon.utils.dungeon.DungeonClass;
 import blade.addon.utils.events.Events;
+import blade.addon.utils.rendering.RenderUtils;
 import config.practical.hud.HUDComponent;
 import net.fabricmc.fabric.api.client.message.v1.ClientReceiveMessageEvents;
 import net.minecraft.client.MinecraftClient;
@@ -82,6 +83,6 @@ public class KeyNotifier {
         int y = component.getScaledY();
 
         Text text = isBloodKey ? BLOOD_KEY : WITHER_KEY;
-        context.drawText(MinecraftClient.getInstance().textRenderer, text, x, y, 0xffffffff, true);
+        RenderUtils.drawCenteredText(context, MinecraftClient.getInstance().textRenderer, text, x, y, component.getWidth(), 0xffffffff);
     }
 }
diff --git a/src/main/java/blade/addon/features/dungeon/RunStartValidator.java b/src/main/java/blade/addon/features/dungeon/RunStartValidator.java
index a247ef7..22323d2 100644
--- a/src/main/java/blade/addon/features/dungeon/RunStartValidator.java
+++ b/src/main/java/blade/addon/features/dungeon/RunStartValidator.java
@@ -3,6 +3,7 @@ package blade.addon.features.dungeon;
 import blade.addon.utils.Location;
 import blade.addon.utils.Scheduler;
 import blade.addon.utils.config.values.Dungeons;
+import blade.addon.utils.data.PartyUtil;
 import blade.addon.utils.dungeon.Phase;
 import blade.addon.utils.events.Events;
 import blade.addon.utils.rendering.RenderUtils;
@@ -33,12 +34,15 @@ public class RunStartValidator {
     private static boolean hasDupeClasses = false;
     private static boolean notEnoughPlayers = false;
 
+    private static boolean hasTicked = false;
+
     public static void init() {
         ClientReceiveMessageEvents.GAME.register((message, overlay) -> {
             if (!Location.inDungeon() || Phase.runStarted() || !Dungeons.detectDuplicateClass) return;
 
             Matcher matcher = STARTING_PATTERN.matcher(message.getString());
             if (!matcher.find()) return;
+            hasTicked = true;
 
             if (validate()) {
                 Scheduler.scheduleSound(SoundEvents.BLOCK_NOTE_BLOCK_PLING.value(), 2, 1);
@@ -47,6 +51,7 @@ public class RunStartValidator {
 
         Events.ON_LOCATION_CHANGE.register(location -> {
             if (Location.inDungeon()) {
+                PartyUtil.sendPacket();
                 reset();
             }
 
@@ -54,6 +59,8 @@ public class RunStartValidator {
         });
 
         Events.ON_PLAYER_ENTRY.register(receivedEntry -> {
+            //will only check after the first game message in case of false positives
+            if (!hasTicked) return false;
             if (!Location.inDungeon() || Phase.runStarted() || (!Dungeons.detectDuplicateClass && !Dungeons.detectPlayerCount)) return false;
             validate();
             return false;
@@ -98,12 +105,13 @@ public class RunStartValidator {
             count += num;
         }
 
-        notEnoughPlayers = count < Dungeons.playersNeeded;
+        notEnoughPlayers = count < PartyUtil.getPlayerCount();
     }
 
     private static void reset() {
         hasDupeClasses = false;
         notEnoughPlayers = false;
+        hasTicked = false;
     }
 
     private static HashMap<String, Integer> readScoreBoard(Scoreboard scoreboard) {
diff --git a/src/main/java/blade/addon/features/dungeon/f7/terms/TitleHider.java b/src/main/java/blade/addon/features/dungeon/f7/terms/TitleHider.java
index a8ce56f..48ad2ff 100644
--- a/src/main/java/blade/addon/features/dungeon/f7/terms/TitleHider.java
+++ b/src/main/java/blade/addon/features/dungeon/f7/terms/TitleHider.java
@@ -26,16 +26,8 @@ public class TitleHider {
             String name = matcher.group(1);
             if (Misc.isClientPlayer(name)) {
                 Misc.forceTitle(Text.empty(), packet.text());
-            } else {
-                try {
-                    int currentCompleted = Integer.parseInt(matcher.group(4));
-                    if (Section.shouldIncrement(currentCompleted)) {
-                        Misc.forceTitle(Text.empty(), packet.text());
-
-                    }
-                } catch (Exception ignored) {
-                }
             }
+            //end of section msg is handled by the Section class currently
         } else {
             Misc.forceTitle(Text.empty(), packet.text());
         }
diff --git a/src/main/java/blade/addon/features/other/SelectedPet.java b/src/main/java/blade/addon/features/other/SelectedPet.java
index 37fd707..3531e2d 100644
--- a/src/main/java/blade/addon/features/other/SelectedPet.java
+++ b/src/main/java/blade/addon/features/other/SelectedPet.java
@@ -2,6 +2,7 @@ package blade.addon.features.other;
 
 import blade.addon.utils.Constants;
 import blade.addon.utils.Debug;
+import blade.addon.utils.Scheduler;
 import blade.addon.utils.config.values.ExtraOptions;
 import blade.addon.utils.events.Events;
 import config.practical.hud.HUDComponent;
@@ -46,7 +47,7 @@ public class SelectedPet {
 
                 String name = matcher.group(2).replace(" ✦", "");
                 Style style = getStyle(message, name);
-                summonPet(name, Text.literal(name).setStyle(style));
+                summonPet(name, Text.literal(name).setStyle(style), false);
                 return;
             }
 
@@ -60,7 +61,7 @@ public class SelectedPet {
                     textName = string.substring(index - COLOR_OFFSET, index) + textName;
                 }
 
-                summonPet(name, Text.literal(textName));
+                summonPet(name, Text.literal(textName), true);
             }
         });
 
@@ -79,7 +80,7 @@ public class SelectedPet {
             if (matcher.find()) {
                 String petName = matcher.group(1).replace(" ✦", "");
                 Style style = getStyle(text, petName);
-                summonPet(petName, Text.literal(petName).setStyle(style));
+                summonPet(petName, Text.literal(petName).setStyle(style), false);
             }
 
             return false;
@@ -94,7 +95,7 @@ public class SelectedPet {
 
     }
 
-    private static void summonPet(String stringName, Text textName) {
+    private static void summonPet(String stringName, Text textName, boolean sendSound) {
         if (stringName.equals(currentPetString)) {
             return;
         }
@@ -104,6 +105,10 @@ public class SelectedPet {
 
         Events.ON_PET.invoke(petEvent -> petEvent.onPet(currentPetString));
         updateSprite(stringName);
+
+        if (sendSound && ExtraOptions.sendOnPetSound) {
+            Scheduler.scheduleSound(ExtraOptions.petSound);
+        }
     }
 
     private static Style getStyle(Text text, String name) {
diff --git a/src/main/java/blade/addon/mixin/ChatHudInvoker.java b/src/main/java/blade/addon/mixin/ChatHudInvoker.java
new file mode 100644
index 0000000..a931d24
--- /dev/null
+++ b/src/main/java/blade/addon/mixin/ChatHudInvoker.java
@@ -0,0 +1,25 @@
+package blade.addon.mixin;
+
+import net.minecraft.client.gui.hud.ChatHud;
+import net.minecraft.client.gui.hud.ChatHudLine;
+import org.spongepowered.asm.mixin.Mixin;
+import org.spongepowered.asm.mixin.gen.Accessor;
+import org.spongepowered.asm.mixin.gen.Invoker;
+
+import java.util.List;
+
+@Mixin(ChatHud.class)
+public interface ChatHudInvoker {
+
+    @Invoker("toChatLineY")
+    double lineY(double y);
+
+    @Invoker("toChatLineX")
+    double lineX(double X);
+
+    @Invoker("getMessageLineIndex")
+    int getLineIndex(double chatLineX, double chatLineY);
+
+    @Accessor("visibleMessages")
+    List<ChatHudLine.Visible> getVisibleMessages();
+}
diff --git a/src/main/java/blade/addon/mixin/ChatScreenMixin.java b/src/main/java/blade/addon/mixin/ChatScreenMixin.java
new file mode 100644
index 0000000..701e788
--- /dev/null
+++ b/src/main/java/blade/addon/mixin/ChatScreenMixin.java
@@ -0,0 +1,75 @@
+package blade.addon.mixin;
+
+import blade.addon.utils.Misc;
+import blade.addon.utils.config.values.ExtraOptions;
+import net.minecraft.client.MinecraftClient;
+import net.minecraft.client.gui.Click;
+import net.minecraft.client.gui.hud.ChatHudLine;
+import net.minecraft.client.gui.screen.ChatScreen;
+import net.minecraft.client.gui.screen.Screen;
+import net.minecraft.text.OrderedText;
+import net.minecraft.text.Text;
+import org.lwjgl.glfw.GLFW;
+import org.spongepowered.asm.mixin.Mixin;
+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.callback.CallbackInfoReturnable;
+
+import java.util.List;
+
+@Mixin(ChatScreen.class)
+public class ChatScreenMixin extends Screen {
+
+    protected ChatScreenMixin(Text title) {
+        super(title);
+    }
+
+    @Inject(method = "mouseClicked", at = @At("HEAD"))
+    private static void mouseClicked(Click click, boolean doubled, CallbackInfoReturnable<Boolean> cir) {
+        if (!ExtraOptions.copyChat || click.button() != GLFW.GLFW_MOUSE_BUTTON_RIGHT) return;
+
+        MinecraftClient mc = MinecraftClient.getInstance();
+        ChatHudInvoker hudInvoker = (ChatHudInvoker) mc.inGameHud.getChatHud();
+        if (hudInvoker == null) return;
+
+        double x = hudInvoker.lineX(click.x());
+        double y = hudInvoker.lineY(click.y());
+
+        String string;
+        if (ExtraOptions.copyLineOnly) {
+            int index = hudInvoker.getLineIndex(x, y);
+            List<ChatHudLine.Visible> visibleMessages = hudInvoker.getVisibleMessages();
+            if (visibleMessages == null || index < 0 || index >= visibleMessages.size()) return;
+
+            ChatHudLine.Visible msg = visibleMessages.get(index);
+            string = orderedTextToString(msg.content());
+        } else {
+            string = Misc.copyChat(hudInvoker, x, y);
+        }
+
+        if (string == null) return;
+
+        if (ExtraOptions.removeColorCodes) {
+            string = string.replaceAll("§.", "");
+        }
+
+        if (ExtraOptions.replaceColorChars) {
+            string = string.replaceAll("§", "&");
+        }
+
+        mc.keyboard.setClipboard(string);
+    }
+
+    @Unique
+    private static String orderedTextToString(OrderedText text) {
+        StringBuilder builder = new StringBuilder();
+
+        text.accept((index, style, codePoint) -> {
+            builder.appendCodePoint(codePoint);
+            return true;
+        });
+
+        return builder.toString();
+    }
+}
diff --git a/src/main/java/blade/addon/mixin/EntityRenderManagerMixin.java b/src/main/java/blade/addon/mixin/EntityRenderManagerMixin.java
index 776ea5d..c7c570b 100644
--- a/src/main/java/blade/addon/mixin/EntityRenderManagerMixin.java
+++ b/src/main/java/blade/addon/mixin/EntityRenderManagerMixin.java
@@ -29,8 +29,9 @@ public class EntityRenderManagerMixin {
             if (clientPlayer != null) {
                 if (player.getId() == clientPlayer.getId()) return;
             }
-            cir.setReturnValue(!HidePlayers.shouldHidePlayers(player));
-
+            if (HidePlayers.shouldHidePlayers(player)) {
+                cir.setReturnValue(false);
+            }
         }
 
         if (!entity.isAlive() && ExtraOptions.hideDeadEntities) {
diff --git a/src/main/java/blade/addon/utils/Location.java b/src/main/java/blade/addon/utils/Location.java
index 5770126..33aa12e 100644
--- a/src/main/java/blade/addon/utils/Location.java
+++ b/src/main/java/blade/addon/utils/Location.java
@@ -40,7 +40,6 @@ public enum Location {
 
         instance.subscribeToEventPacket(ClientboundLocationPacket.class);
 
-
     }
 
     public static boolean in(Location location) {
diff --git a/src/main/java/blade/addon/utils/Misc.java b/src/main/java/blade/addon/utils/Misc.java
index ea47dc8..6ee4adc 100644
--- a/src/main/java/blade/addon/utils/Misc.java
+++ b/src/main/java/blade/addon/utils/Misc.java
@@ -1,9 +1,11 @@
 package blade.addon.utils;
 
+import blade.addon.mixin.ChatHudInvoker;
 import blade.addon.utils.config.values.ExtraOptions;
 import blade.addon.utils.interfaces.GameHud;
 import net.minecraft.client.MinecraftClient;
 import net.minecraft.client.gui.hud.ChatHud;
+import net.minecraft.client.gui.hud.ChatHudLine;
 import net.minecraft.client.gui.hud.InGameHud;
 import net.minecraft.client.network.ClientPlayNetworkHandler;
 import net.minecraft.client.network.ClientPlayerEntity;
@@ -103,11 +105,52 @@ public class Misc {
         return false;
     }
 
-    private static void forceMainThread(Runnable runnable) {
+    public static void forceMainThread(Runnable runnable) {
         if (INSTANCE.isOnThread()) {
             runnable.run();
         } else {
             INSTANCE.executeSync(runnable);
         }
     }
+
+    public static String copyChat(ChatHudInvoker hudInvoker, double x, double y) {
+        List<ChatHudLine.Visible> messages = hudInvoker.getVisibleMessages();
+        int endIndex = hudInvoker.getLineIndex(x, y);
+
+        if (messages == null || endIndex < 0 || endIndex >= messages.size()) return null;
+
+        StringBuilder tempBuilder = new StringBuilder();
+
+        int startIndex = endIndex;
+
+        //find start of msg
+        for (int i = endIndex; i >= 0; i--) {
+            ChatHudLine.Visible chatHudLine = messages.get(i);
+            if (chatHudLine.endOfEntry()) {
+                startIndex = i;
+                break;
+            }
+        }
+
+        if (!messages.get(endIndex).endOfEntry()) {
+            //find end of msg
+            for (int i = endIndex + 1; i < messages.size(); i++) {
+                ChatHudLine.Visible chatHudLine = messages.get(i);
+                if (chatHudLine.endOfEntry()) {
+                    endIndex = i - 1;
+                    break;
+                }
+            }
+        }
+        StringBuilder builder = new StringBuilder();
+        for (int i = endIndex; i >= startIndex; i--) {
+            ChatHudLine.Visible chatHudLine = messages.get(i);
+
+            chatHudLine.content().accept((index, style, codePoint) -> {
+                builder.appendCodePoint(codePoint);
+                return true;
+            });
+        }
+        return builder.toString();
+    }
 }
diff --git a/src/main/java/blade/addon/utils/config/Config.java b/src/main/java/blade/addon/utils/config/Config.java
index 4c49e60..1811bb7 100644
--- a/src/main/java/blade/addon/utils/config/Config.java
+++ b/src/main/java/blade/addon/utils/config/Config.java
@@ -47,7 +47,6 @@ public class Config {
         start.add(new ConfigBool(Text.literal("Dupe class warning"), () -> Dungeons.detectDuplicateClass, bool -> Dungeons.detectDuplicateClass = bool));
         start.add(new ConfigBool(Text.literal("Ignore dupe mage"), () -> Dungeons.ignoreDupeMage, bool -> Dungeons.ignoreDupeMage = bool));
         start.add(new ConfigBool(Text.literal("Player count warning"), () -> Dungeons.detectPlayerCount, bool -> Dungeons.detectPlayerCount = bool));
-        start.add(new ConfigInt(Text.literal("Players needed"), () -> Dungeons.playersNeeded, count -> Dungeons.playersNeeded = count, 1, 1, 5));
         dungeons.add(start);
 
         ConfigSection invincibility = new ConfigSection(Text.literal("Invincibility Timer"));
@@ -169,6 +168,8 @@ public class Config {
         splits.add(new ConfigBool(Text.literal("Include total time"), () -> Phase.includeTotalTime, bool -> Phase.includeTotalTime = bool));
         splits.add(new ConfigBool(Text.literal("Send split in chat when over"), () -> Phase.sendSplitInChat, bool -> Phase.sendSplitInChat = bool));
         splits.add(new ConfigOptions<>(Text.literal("Tick timer type"), Split.TimerType.values(), () -> Split.timerType, type -> Split.timerType = type));
+        splits.add(new ConfigBool(Text.literal("Only show activated splits"), () -> Phase.onlyShowActivatedSplits, bool -> Phase.onlyShowActivatedSplits = bool));
+
 
         splits.add(new ConfigColor(Text.literal("Real time color (Inactive)"), () -> Split.realTimeColorInactive, color -> Split.realTimeColorInactive = color, "real-time-inactive", false));
         splits.add(new ConfigColor(Text.literal("Real time color (Ongoing)"), () -> Split.realTimeColorOngoing, color -> Split.realTimeColorOngoing = color, "real-time-ongoing", false));
@@ -239,10 +240,19 @@ public class Config {
         extra.add(new ConfigBool(Text.literal("Compact hoppity messages"), () -> ExtraOptions.compactHoppityMsgs, bool -> ExtraOptions.compactHoppityMsgs = bool));
         extra.add(new ConfigBool(Text.literal("Disable recipe book"), () -> ExtraOptions.disableRecipeBook, bool -> ExtraOptions.disableRecipeBook = bool));
 
+        ConfigSection copyChat = new ConfigSection(Text.literal("Copy chat"));
+        copyChat.add(new ConfigBool(Text.literal("Copy with right click"), () -> ExtraOptions.copyChat, bool -> ExtraOptions.copyChat = bool));
+        copyChat.add(new ConfigBool(Text.literal("remove color codes"), () -> ExtraOptions.removeColorCodes, bool -> ExtraOptions.removeColorCodes = bool));
+        copyChat.add(new ConfigBool(Text.literal("replace format char with &"), () -> ExtraOptions.replaceColorChars, bool -> ExtraOptions.replaceColorChars = bool));
+        copyChat.add(new ConfigBool(Text.literal("copy line only"), () -> ExtraOptions.copyLineOnly, bool -> ExtraOptions.copyLineOnly = bool));
+        extra.add(copyChat);
+
         ConfigSection pets = new ConfigSection(Text.literal("Pets"));
-        pets.add(new ConfigBool(Text.literal("Highlight selected pet"), () -> ExtraOptions.highlightSelectedPet, bool -> ExtraOptions.highlightSelectedPet = bool));
+        pets.add(new ConfigBool(Text.literal("Highlight selected pet in menu"), () -> ExtraOptions.highlightSelectedPet, bool -> ExtraOptions.highlightSelectedPet = bool));
         pets.add(new ConfigBool(Text.literal("Draw selected pet"), () -> ExtraOptions.drawPetHUD, bool -> ExtraOptions.drawPetHUD = bool));
         pets.add(new ConfigBool(Text.literal("Draw the pets sprite"), () -> ExtraOptions.includePetSprite, bool -> ExtraOptions.includePetSprite = bool));
+        pets.add(new ConfigBool(Text.literal("Send sound on petswap"), () -> ExtraOptions.sendOnPetSound, bool -> ExtraOptions.sendOnPetSound = bool));
+        pets.add(new ConfigSound(Text.literal("Pet swap sound"), ExtraOptions.petSound));
         extra.add(pets);
 
         ConfigSection diana = new ConfigSection(Text.literal("Diana notifications"));
diff --git a/src/main/java/blade/addon/utils/config/values/Dungeons.java b/src/main/java/blade/addon/utils/config/values/Dungeons.java
index 783cfd8..d092970 100644
--- a/src/main/java/blade/addon/utils/config/values/Dungeons.java
+++ b/src/main/java/blade/addon/utils/config/values/Dungeons.java
@@ -97,7 +97,4 @@ public class Dungeons {
 
     @ConfigValue
     public static boolean detectPlayerCount = false;
-
-    @ConfigValue
-    public static int playersNeeded = 5;
 }
diff --git a/src/main/java/blade/addon/utils/config/values/ExtraOptions.java b/src/main/java/blade/addon/utils/config/values/ExtraOptions.java
index f2b6968..62de392 100644
--- a/src/main/java/blade/addon/utils/config/values/ExtraOptions.java
+++ b/src/main/java/blade/addon/utils/config/values/ExtraOptions.java
@@ -92,4 +92,22 @@ public class ExtraOptions {
 
     @ConfigValue
     public static SoundData ragSound = new SoundData(SoundEvents.BLOCK_NOTE_BLOCK_PLING.value(), 1, 1);
+
+    @ConfigValue
+    public static boolean sendOnPetSound = false;
+
+    @ConfigValue
+    public static  SoundData petSound = new SoundData(SoundEvents.BLOCK_NOTE_BLOCK_PLING.value(), 1, 1);
+
+    @ConfigValue
+    public static boolean copyChat = false;
+
+    @ConfigValue
+    public static boolean removeColorCodes = false;
+
+    @ConfigValue
+    public static boolean replaceColorChars = false;
+
+    @ConfigValue
+    public static boolean copyLineOnly = false;
 }
diff --git a/src/main/java/blade/addon/utils/data/PartyUtil.java b/src/main/java/blade/addon/utils/data/PartyUtil.java
new file mode 100644
index 0000000..ea13b7b
--- /dev/null
+++ b/src/main/java/blade/addon/utils/data/PartyUtil.java
@@ -0,0 +1,53 @@
+package blade.addon.utils.data;
+
+import blade.addon.utils.Debug;
+import net.hypixel.modapi.HypixelModAPI;
+import net.hypixel.modapi.packet.impl.clientbound.ClientboundPartyInfoPacket;
+import net.hypixel.modapi.packet.impl.serverbound.ServerboundPartyInfoPacket;
+
+import java.util.Map;
+import java.util.UUID;
+
+public class PartyUtil {
+
+    private static final int MIN_DELAY = 1000 * 60;
+    private static final HypixelModAPI INSTANCE = HypixelModAPI.getInstance();
+
+    private static long grabbedTime = 0;
+    private static Map<UUID, ClientboundPartyInfoPacket.PartyMember> memberMap;
+    private static boolean inParty;
+
+
+    public static void init() {
+        INSTANCE.createHandler(ClientboundPartyInfoPacket.class, packet -> {
+            Debug.LOGGER.info("Received party info packet");
+            memberMap = packet.getMemberMap();
+            inParty = packet.isInParty();
+        });
+    }
+
+    public static void sendPacket() {
+        if (INSTANCE == null) {
+            throw new IllegalStateException("Instance not set");
+        }
+
+        if (System.currentTimeMillis() - grabbedTime < MIN_DELAY) return;
+        if (INSTANCE.sendPacket(new ServerboundPartyInfoPacket())) {
+            grabbedTime = System.currentTimeMillis();
+        } else {
+            Debug.LOGGER.warn("Server bound party info packet lost");
+        }
+    }
+
+    public static int getPlayerCount() {
+        sendPacket();
+        if (!inParty) return 0;
+        return memberMap.size();
+    }
+
+    public static boolean isInParty() {
+        sendPacket();
+        return inParty;
+    }
+
+}
diff --git a/src/main/java/blade/addon/utils/dungeon/Phase.java b/src/main/java/blade/addon/utils/dungeon/Phase.java
index e1e5d37..2d30f3a 100644
--- a/src/main/java/blade/addon/utils/dungeon/Phase.java
+++ b/src/main/java/blade/addon/utils/dungeon/Phase.java
@@ -47,6 +47,9 @@ public class Phase {
     @ConfigValue
     public static boolean sendSplitInChat = false;
 
+    @ConfigValue
+    public static boolean onlyShowActivatedSplits = false;
+
     public static void init() {
         Events.ON_SERVER_TICK.register(() -> {
             if (currentSplits == null || runOver) return false;
@@ -230,9 +233,13 @@ public class Phase {
                     int splitCount = currentSplits.size();
                     if (!includeTotalTime) splitCount--;
 
-                    for (int i = 0; i < splitCount; i++) {
-                        currentSplits.get(i).drawSplit(drawContext, textRenderer, x, y + Constants.TEXT_HEIGHT * i, SPLIT_LENGTH);
+                    int height = 0;
 
+                    for (int i = 0; i < splitCount; i++) {
+                        Split split = currentSplits.get(i);
+                        if (Phase.onlyShowActivatedSplits && !(split.started() || split.ended())) continue;
+                        split.drawSplit(drawContext, textRenderer, x, y + Constants.TEXT_HEIGHT * height, SPLIT_LENGTH);
+                        height++;
                     }
                 } else {
                     for (int i = 0; i < DUMMY_SIZE; i++) {
diff --git a/src/main/java/blade/addon/utils/dungeon/Section.java b/src/main/java/blade/addon/utils/dungeon/Section.java
index 6e0d87e..ad8591a 100644
--- a/src/main/java/blade/addon/utils/dungeon/Section.java
+++ b/src/main/java/blade/addon/utils/dungeon/Section.java
@@ -14,6 +14,7 @@ import net.minecraft.client.MinecraftClient;
 import net.minecraft.client.font.TextRenderer;
 import net.minecraft.client.gui.DrawContext;
 import net.minecraft.text.Text;
+import net.minecraft.util.Formatting;
 
 import java.util.List;
 import java.util.regex.Matcher;
@@ -74,6 +75,9 @@ public class Section {
                 currentSection = 1;
                 splits[0].start();
             } else if (Phase.inGoldorTunnel()) {
+                if (TitleHider.shouldHideTitle()) {
+                    Misc.forceTitle(Text.empty(), Text.literal("The Core entrance is opening!").formatted(Formatting.GREEN));
+                }
                 if (Debug.termInfo) {
                     Misc.addChatMessage(Text.literal("Terminals ended"));
                 }
@@ -168,6 +172,7 @@ public class Section {
 
             if (shouldIncrement(currentCompleted)) {
                 incrementSection();
+                Misc.forceTitle(Text.empty(), message);
             } else {
                 total = totalNeeded;
                 completed = currentCompleted;
diff --git a/src/main/resources/blade-addons.mixins.json b/src/main/resources/blade-addons.mixins.json
index d50871a..431e836 100644
--- a/src/main/resources/blade-addons.mixins.json
+++ b/src/main/resources/blade-addons.mixins.json
@@ -19,6 +19,8 @@
     "requireAnnotations": true
   },
   "client": [
+    "ChatHudInvoker",
+    "ChatScreenMixin",
     "ClientPlayerEntityMixin",
     "ClientPlayNetworkHandlerMixin",
     "EntityRendererMixin",
Merge pull request #2 from athenameow/new
Updated SelectPet.java
Blade · 2026-01-03 · 8ab64db
查看变更
src/main/java/blade/addon/features/other/SelectedPet.java | 6 +++---
 1 file changed, 3 insertions(+), 3 deletions(-)
Merge branch 'new' into newBlade · 2026-01-03 · 8dcfccf
查看变更
src/main/java/blade/addon/Bladeaddons.java                          |   2 ++
 src/main/java/blade/addon/features/dungeon/f7/terms/TitleHider.java |  35 +++++++++++++++++++++++++++
 src/main/java/blade/addon/features/highlight/MimicHighlight.java    |   1 +
 src/main/java/blade/addon/features/highlight/MobHighlight.java      |  28 ++++++++++++----------
 src/main/java/blade/addon/features/highlight/SheepHighlight.java    | 110 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 src/main/java/blade/addon/mixin/ClientPlayNetworkHandlerMixin.java  |   9 +++++++
 src/main/java/blade/addon/mixin/EntityRenderManagerMixin.java       |   9 +++++++
 src/main/java/blade/addon/utils/Misc.java                           |   9 +++++++
 src/main/java/blade/addon/utils/config/Config.java                  |  21 ++++++++++++----
 src/main/java/blade/addon/utils/dungeon/Section.java                |  34 +++++++++++++-------------
 src/main/java/blade/addon/utils/dungeon/Split.java                  |   4 ++--
 src/main/java/blade/addon/utils/rendering/RenderUtils.java          |   1 -
 src/main/resources/blade-addons.mixins.json                         |   2 +-
 13 files changed, 226 insertions(+), 39 deletions(-)
- improved title hiderGabriel Ellnebrand · 2026-01-03 · a2fba57
查看变更
src/main/java/blade/addon/features/dungeon/f7/terms/TitleHider.java | 35 +++++++++++++++++++++++++++++++++++
 src/main/java/blade/addon/mixin/ClientPlayNetworkHandlerMixin.java  |  9 +++++++++
 src/main/java/blade/addon/utils/dungeon/Section.java                | 19 ++++++-------------
 3 files changed, 50 insertions(+), 13 deletions(-)
diff --git a/src/main/java/blade/addon/features/dungeon/f7/terms/TitleHider.java b/src/main/java/blade/addon/features/dungeon/f7/terms/TitleHider.java
index 92e7cf2..a8ce56f 100644
--- a/src/main/java/blade/addon/features/dungeon/f7/terms/TitleHider.java
+++ b/src/main/java/blade/addon/features/dungeon/f7/terms/TitleHider.java
@@ -1,11 +1,46 @@
 package blade.addon.features.dungeon.f7.terms;
 
 import blade.addon.utils.Location;
+import blade.addon.utils.Misc;
 import blade.addon.utils.config.values.Floor7;
 import blade.addon.utils.dungeon.Phase;
+import blade.addon.utils.dungeon.Section;
+import net.minecraft.network.packet.s2c.play.SubtitleS2CPacket;
+import net.minecraft.text.Text;
+
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
 
 public class TitleHider {
 
+    private static final Pattern TERMINALS_DONE_PATTERN = Pattern.compile("^(\\w+) (activated|completed) a (terminal|device|lever)! \\((\\d)/(\\d)\\)$");
+
+    public static void processSubtitle(SubtitleS2CPacket packet) {
+        if (!shouldHideTitle()) return;
+        if (Section.inSection(1) && Pre4Notifier.atDev()) return;
+        String string = packet.text().getString().replaceAll("§.", "");
+
+
+        Matcher matcher = TERMINALS_DONE_PATTERN.matcher(string);
+        if (matcher.find()) {
+            String name = matcher.group(1);
+            if (Misc.isClientPlayer(name)) {
+                Misc.forceTitle(Text.empty(), packet.text());
+            } else {
+                try {
+                    int currentCompleted = Integer.parseInt(matcher.group(4));
+                    if (Section.shouldIncrement(currentCompleted)) {
+                        Misc.forceTitle(Text.empty(), packet.text());
+
+                    }
+                } catch (Exception ignored) {
+                }
+            }
+        } else {
+            Misc.forceTitle(Text.empty(), packet.text());
+        }
+    }
+
     public static boolean shouldHideTitle() {
         return Floor7.hideTerminalTitles && Phase.inP3() && Location.inDungeon();
     }
diff --git a/src/main/java/blade/addon/mixin/ClientPlayNetworkHandlerMixin.java b/src/main/java/blade/addon/mixin/ClientPlayNetworkHandlerMixin.java
index 70cbb4b..5afad5d 100644
--- a/src/main/java/blade/addon/mixin/ClientPlayNetworkHandlerMixin.java
+++ b/src/main/java/blade/addon/mixin/ClientPlayNetworkHandlerMixin.java
@@ -1,5 +1,6 @@
 package blade.addon.mixin;
 
+import blade.addon.features.dungeon.f7.terms.TitleHider;
 import blade.addon.utils.Debug;
 import blade.addon.utils.Misc;
 import blade.addon.utils.config.values.ExtraOptions;
@@ -17,6 +18,7 @@ import net.minecraft.network.packet.s2c.play.EntitySpawnS2CPacket;
 import net.minecraft.network.packet.s2c.play.EntityTrackerUpdateS2CPacket;
 import net.minecraft.network.packet.s2c.play.PlaySoundS2CPacket;
 import net.minecraft.network.packet.s2c.play.PlayerListS2CPacket;
+import net.minecraft.network.packet.s2c.play.SubtitleS2CPacket;
 import net.minecraft.network.packet.s2c.play.TeamS2CPacket;
 import net.minecraft.scoreboard.Team;
 import net.minecraft.sound.SoundEvent;
@@ -79,6 +81,13 @@ public class ClientPlayNetworkHandlerMixin {
         }
     }
 
+    @Inject(method = "onSubtitle", at = @At("HEAD"))
+    public void onTitle(SubtitleS2CPacket packet, CallbackInfo ci) {
+        TitleHider.processSubtitle(packet);
+    }
+
+
+
     @Inject(method = "onEntitySpawn", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/network/ClientPlayNetworkHandler;playSpawnSound(Lnet/minecraft/entity/Entity;)V"))
     public void onEntitySpawn(EntitySpawnS2CPacket packet, CallbackInfo ci, @Local Entity entity) {
         if (entity instanceof PlayerEntity player) {
diff --git a/src/main/java/blade/addon/utils/dungeon/Section.java b/src/main/java/blade/addon/utils/dungeon/Section.java
index 61cbf40..6e0d87e 100644
--- a/src/main/java/blade/addon/utils/dungeon/Section.java
+++ b/src/main/java/blade/addon/utils/dungeon/Section.java
@@ -14,7 +14,6 @@ import net.minecraft.client.MinecraftClient;
 import net.minecraft.client.font.TextRenderer;
 import net.minecraft.client.gui.DrawContext;
 import net.minecraft.text.Text;
-import net.minecraft.util.Formatting;
 
 import java.util.List;
 import java.util.regex.Matcher;
@@ -75,9 +74,6 @@ public class Section {
                 currentSection = 1;
                 splits[0].start();
             } else if (Phase.inGoldorTunnel()) {
-                if (TitleHider.shouldHideTitle()) {
-                    Misc.forceTitle(Text.empty(), Text.literal("The Core entrance is opening!").formatted(Formatting.GREEN));
-                }
                 if (Debug.termInfo) {
                     Misc.addChatMessage(Text.literal("Terminals ended"));
                 }
@@ -170,18 +166,11 @@ public class Section {
             Events.ON_TERMINAL.invoke(terminalEvent -> terminalEvent.onComplete(name, action, objective, currentCompleted, totalNeeded));
 
 
-            int recentlyCompleted = currentCompleted;
-            if ((recentlyCompleted == total && gateBlownUp) || (recentlyCompleted < completed)) {
+            if (shouldIncrement(currentCompleted)) {
                 incrementSection();
-                if (TitleHider.shouldHideTitle()) {
-                    Misc.forceTitle(Text.empty(), message);
-                }
             } else {
-                if (Misc.isClientPlayer(name) && TitleHider.shouldHideTitle()) {
-                    Misc.forceTitle(Text.empty(), message);
-                }
                 total = totalNeeded;
-                completed = recentlyCompleted;
+                completed = currentCompleted;
             }
 
             if (Floor7.terminalTimeStamps) {
@@ -255,6 +244,10 @@ public class Section {
         return false;
     }
 
+    public static boolean shouldIncrement(int recentlyCompleted) {
+        return (recentlyCompleted == total && gateBlownUp) || (recentlyCompleted < completed);
+    }
+
     public static void render(HUDComponent component, DrawContext context) {
         int x = component.getScaledX();
         int y = component.getScaledY();
- changed selectedpet regex to ones that work with some extra pet skins - sent by athenameow - added guided sheep hider and highlighter - fixed terminal time stamp - fixed some typosGabriel Ellnebrand · 2026-01-02 · 22301b5
查看变更
src/main/java/blade/addon/Bladeaddons.java                       |   2 ++
 src/main/java/blade/addon/features/highlight/MimicHighlight.java |   1 +
 src/main/java/blade/addon/features/highlight/MobHighlight.java   |  28 +++++++++++-----------
 src/main/java/blade/addon/features/highlight/SheepHighlight.java | 110 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 src/main/java/blade/addon/features/other/SelectedPet.java        |   6 ++---
 src/main/java/blade/addon/mixin/EntityRenderManagerMixin.java    |   9 ++++++++
 src/main/java/blade/addon/utils/Misc.java                        |   9 ++++++++
 src/main/java/blade/addon/utils/config/Config.java               |  21 +++++++++++++----
 src/main/java/blade/addon/utils/dungeon/Section.java             |  15 ++++++++----
 src/main/java/blade/addon/utils/dungeon/Split.java               |   4 ++--
 src/main/java/blade/addon/utils/rendering/RenderUtils.java       |   1 -
 src/main/resources/blade-addons.mixins.json                      |   2 +-
 12 files changed, 179 insertions(+), 29 deletions(-)
diff --git a/src/main/java/blade/addon/Bladeaddons.java b/src/main/java/blade/addon/Bladeaddons.java
index 056e6d9..00a237f 100644
--- a/src/main/java/blade/addon/Bladeaddons.java
+++ b/src/main/java/blade/addon/Bladeaddons.java
@@ -28,6 +28,7 @@ import blade.addon.features.dungeon.f7.terms.Pre4Notifier;
 import blade.addon.features.dungeon.f7.terms.TermStartTimer;
 import blade.addon.features.highlight.MimicHighlight;
 import blade.addon.features.highlight.MobHighlight;
+import blade.addon.features.highlight.SheepHighlight;
 import blade.addon.features.item.DropAnimation;
 import blade.addon.features.other.CompactHoppity;
 import blade.addon.features.other.DianaNotifier;
@@ -101,5 +102,6 @@ public class Bladeaddons implements ModInitializer {
         CompactHoppity.init();
         DropAnimation.init();
         MimicHighlight.init();
+        SheepHighlight.init();
     }
 }
\ No newline at end of file
diff --git a/src/main/java/blade/addon/features/highlight/MimicHighlight.java b/src/main/java/blade/addon/features/highlight/MimicHighlight.java
index 6b5bd13..cf5d258 100644
--- a/src/main/java/blade/addon/features/highlight/MimicHighlight.java
+++ b/src/main/java/blade/addon/features/highlight/MimicHighlight.java
@@ -83,6 +83,7 @@ public class MimicHighlight {
 
         MatrixStack.Entry entry = matrices.peek();
         float[] rgba = RenderUtils.toFloats(MobHighlight.mimicOutlineColor);
+
         mimicList.forEach(blockEntity -> {
             Box renderBox = box.offset(blockEntity.getPos());
             VertexRendering.drawBox(entry, outlineConsumer, renderBox, rgba[0], rgba[1], rgba[2], rgba[3]);
diff --git a/src/main/java/blade/addon/features/highlight/MobHighlight.java b/src/main/java/blade/addon/features/highlight/MobHighlight.java
index 1f238ba..86484b1 100644
--- a/src/main/java/blade/addon/features/highlight/MobHighlight.java
+++ b/src/main/java/blade/addon/features/highlight/MobHighlight.java
@@ -36,7 +36,6 @@ import net.minecraft.item.Items;
 import net.minecraft.text.Text;
 import net.minecraft.text.TextColor;
 import net.minecraft.util.math.Box;
-import net.minecraft.util.math.MathHelper;
 import net.minecraft.util.math.Vec3d;
 
 import java.util.ArrayList;
@@ -93,6 +92,10 @@ public class MobHighlight {
     public static double witherExtraWidth = 0;
     @ConfigValue
     public static boolean highlightMimicChests = true;
+    @ConfigValue
+    public static boolean highlightSheep = false;
+    @ConfigValue
+    public static boolean hideSheep = false;
 
     @ConfigValue
     public static int outlineWidth = 4;
@@ -113,6 +116,8 @@ public class MobHighlight {
     public static int witherFilledColor = 0x88222222;
     @ConfigValue
     public static int mimicFilledColor = 0xffffffff;
+    @ConfigValue
+    public static int sheepFilledColor = 0xffffffff;
 
     @ConfigValue
     public static int starOutlineColor = 0xff00ff00;
@@ -130,6 +135,9 @@ public class MobHighlight {
     public static int witherOutlineColor = 0x88222222;
     @ConfigValue
     public static int mimicOutlineColor = 0xffffffff;
+    @ConfigValue
+    public static int sheepOutlineColor = 0xffffffff;
+
 
 
     public static void init() {
@@ -392,9 +400,9 @@ public class MobHighlight {
         return currentHighlight == HighlightType.BOTH || currentHighlight == HighlightType.OUTLINE;
     }
 
-    private static Box getBox(Entity entity, double x, double y, double z) {
+    private static Box getBox(Entity entity, Vec3d pos) {
         EntityDimensions dimension = entity.getDimensions(entity.getPose());
-        Box box = dimension.getBoxAt(x, y, z);
+        Box box = dimension.getBoxAt(pos);
 
         if (entity instanceof WitherEntity) {
             box = box.expand(MobHighlight.witherExtraWidth, 0, MobHighlight.witherExtraWidth);
@@ -434,11 +442,9 @@ public class MobHighlight {
 
             if (entity.isInvisible() && MobHighlight.dontShowInvisibleMobs && entity instanceof PlayerEntity) return;
 
-            double x = MathHelper.lerp(tickProgress, entity.lastRenderX, entity.getX());
-            double y = MathHelper.lerp(tickProgress, entity.lastRenderY, entity.getY());
-            double z = MathHelper.lerp(tickProgress, entity.lastRenderZ, entity.getZ());
+            Vec3d pos = Misc.getPos(entity, tickProgress);
 
-            Box box = getBox(entity, x, y, z);
+            Box box = getBox(entity, pos);
 
 
             int filledColor = MobHighlight.getFilledColor(entity);
@@ -472,12 +478,8 @@ public class MobHighlight {
 
             if (entity.isInvisible() && MobHighlight.dontShowInvisibleMobs && entity instanceof PlayerEntity) return;
 
-            double x = MathHelper.lerp(tickProgress, entity.lastRenderX, entity.getX());
-            double y = MathHelper.lerp(tickProgress, entity.lastRenderY, entity.getY());
-            double z = MathHelper.lerp(tickProgress, entity.lastRenderZ, entity.getZ());
-
-            Box box = getBox(entity, x, y, z);
-
+            Vec3d pos = Misc.getPos(entity, tickProgress);
+            Box box = getBox(entity, pos);
 
             int outlineColor = MobHighlight.getOutlineColor(entity);
             float[] rgba = RenderUtils.toFloats(outlineColor);
diff --git a/src/main/java/blade/addon/features/highlight/SheepHighlight.java b/src/main/java/blade/addon/features/highlight/SheepHighlight.java
new file mode 100644
index 0000000..d55d834
--- /dev/null
+++ b/src/main/java/blade/addon/features/highlight/SheepHighlight.java
@@ -0,0 +1,110 @@
+package blade.addon.features.highlight;
+
+import blade.addon.utils.Location;
+import blade.addon.utils.Misc;
+import blade.addon.utils.events.Events;
+import blade.addon.utils.rendering.RenderLayers;
+import blade.addon.utils.rendering.RenderUtils;
+import net.fabricmc.fabric.api.client.event.lifecycle.v1.ClientTickEvents;
+import net.fabricmc.fabric.api.client.rendering.v1.world.WorldRenderContext;
+import net.fabricmc.fabric.api.client.rendering.v1.world.WorldRenderEvents;
+import net.minecraft.client.MinecraftClient;
+import net.minecraft.client.render.VertexConsumer;
+import net.minecraft.client.render.VertexConsumerProvider;
+import net.minecraft.client.render.VertexRendering;
+import net.minecraft.client.util.math.MatrixStack;
+import net.minecraft.entity.EntityDimensions;
+import net.minecraft.entity.passive.SheepEntity;
+import net.minecraft.util.math.Box;
+import net.minecraft.util.math.Vec3d;
+
+import java.util.concurrent.ConcurrentLinkedQueue;
+
+public class SheepHighlight {
+
+    private static final ConcurrentLinkedQueue<SheepEntity> sheepList = new ConcurrentLinkedQueue<>();
+
+    public static void init() {
+
+        Events.ON_ENTITY_TRACKED.register((entity, world) -> {
+            if (!Location.inDungeon()) return false;
+            if (entity instanceof SheepEntity sheep) {
+                sheepList.add(sheep);
+            }
+            return false;
+        });
+
+        Events.ON_LOCATION_CHANGE.register(location -> {
+            sheepList.clear();
+            return false;
+        });
+
+        ClientTickEvents.END_CLIENT_TICK.register(client -> sheepList.removeIf(SheepEntity::isRemoved));
+
+        WorldRenderEvents.AFTER_ENTITIES.register(SheepHighlight::renderFilled);
+        WorldRenderEvents.AFTER_ENTITIES.register(SheepHighlight::renderOutline);
+    }
+
+    private static void renderFilled(WorldRenderContext context) {
+        if (!MobHighlight.highlightSheep) return;
+        if (!MobHighlight.renderFilled()) return;
+        Vec3d camera = context.worldState().cameraRenderState.pos;
+        MatrixStack matrices = context.matrices();
+        if (matrices == null) return;
+        matrices.push();
+        matrices.translate(-camera.x, -camera.y, -camera.z);
+
+        VertexConsumerProvider consumers = context.consumers();
+        if (consumers == null) return;
+        VertexConsumer filledConsumer = consumers.getBuffer(RenderLayers.FILLED_ENTITY_LAYER);
+
+        double tickProgress = MinecraftClient.getInstance().getRenderTickCounter().getTickProgress(false);
+
+        float[] rgba = RenderUtils.toFloats(MobHighlight.sheepFilledColor);
+        sheepList.forEach(entity -> {
+
+            Vec3d pos = Misc.getPos(entity, tickProgress);
+
+            EntityDimensions dimension = entity.getDimensions(entity.getPose());
+            Box box = dimension.getBoxAt(pos);
+
+            VertexRendering.drawFilledBox(matrices, filledConsumer, box.minX, box.minY, box.minZ, box.maxX, box.maxY, box.maxZ, rgba[0], rgba[1], rgba[2], rgba[3]);
+        });
+
+        matrices.pop();
+
+    }
+
+    private static void renderOutline(WorldRenderContext context) {
+        if (!MobHighlight.highlightSheep) return;
+        if (!MobHighlight.renderOutline()) return;
+        Vec3d camera = context.worldState().cameraRenderState.pos;
+        MatrixStack matrices = context.matrices();
+        if (matrices == null) return;
+        matrices.push();
+        matrices.translate(-camera.x, -camera.y, -camera.z);
+
+        VertexConsumerProvider consumers = context.consumers();
+        if (consumers == null) return;
+        VertexConsumer outlineConsumer = consumers.getBuffer(RenderLayers.getOutline(MobHighlight.outlineWidth));
+
+        double tickProgress = MinecraftClient.getInstance().getRenderTickCounter().getTickProgress(false);
+
+        MatrixStack.Entry entry = matrices.peek();
+        float[] rgba = RenderUtils.toFloats(MobHighlight.sheepOutlineColor);
+
+        sheepList.forEach(entity -> {
+
+            Vec3d pos = Misc.getPos(entity, tickProgress);
+
+            EntityDimensions dimension = entity.getDimensions(entity.getPose());
+            Box box = dimension.getBoxAt(pos);
+
+            VertexRendering.drawBox(entry, outlineConsumer, box, rgba[0], rgba[1], rgba[2], rgba[3]);
+        });
+
+        matrices.pop();
+
+    }
+
+}
diff --git a/src/main/java/blade/addon/features/other/SelectedPet.java b/src/main/java/blade/addon/features/other/SelectedPet.java
index e57a2d5..1dc2825 100644
--- a/src/main/java/blade/addon/features/other/SelectedPet.java
+++ b/src/main/java/blade/addon/features/other/SelectedPet.java
@@ -22,9 +22,9 @@ public class SelectedPet {
 
     private static final int COLOR_OFFSET = 2;
 
-    private static final Pattern TAB_PET_PATTERN = Pattern.compile(" \\[Lvl \\d+] (\\D+)");
-    private static final Pattern MANUAL_EQUIP_PET_PATTERN = Pattern.compile("^You (summoned|despawned) your (\\D+)!$");
-    private static final Pattern RULE_EQUIP_PET_PATTERN = Pattern.compile("^Autopet equipped your \\[Lvl [0-9]+] (\\D+)! VIEW RULE$");
+    private static final Pattern TAB_PET_PATTERN = Pattern.compile(" \\[Lvl \\d+] (?:\\[[^]]+] )?(.+)");
+    private static final Pattern MANUAL_EQUIP_PET_PATTERN = Pattern.compile("^You (summoned|despawned) your (?:\\[Lvl \\d+] )?(?:\\[[^]]+] )?(.+)!$");
+    private static final Pattern RULE_EQUIP_PET_PATTERN = Pattern.compile("^Autopet equipped your \\[Lvl \\d+] (?:\\[[^]]+] )?(.+)! VIEW RULE$");
 
     private static final Text NO_PET = Text.literal("§cNo pet");
     private static final String identifierPrefix = "pets/";
diff --git a/src/main/java/blade/addon/mixin/EntityRenderManagerMixin.java b/src/main/java/blade/addon/mixin/EntityRenderManagerMixin.java
index b8fa133..776ea5d 100644
--- a/src/main/java/blade/addon/mixin/EntityRenderManagerMixin.java
+++ b/src/main/java/blade/addon/mixin/EntityRenderManagerMixin.java
@@ -2,6 +2,8 @@ package blade.addon.mixin;
 
 import blade.addon.features.dungeon.HidePlayers;
 import blade.addon.features.dungeon.ItemHighlight;
+import blade.addon.features.highlight.MobHighlight;
+import blade.addon.utils.Location;
 import blade.addon.utils.config.values.Dungeons;
 import blade.addon.utils.config.values.ExtraOptions;
 import net.minecraft.client.MinecraftClient;
@@ -10,6 +12,7 @@ import net.minecraft.client.render.Frustum;
 import net.minecraft.client.render.entity.EntityRenderManager;
 import net.minecraft.entity.Entity;
 import net.minecraft.entity.ItemEntity;
+import net.minecraft.entity.passive.SheepEntity;
 import net.minecraft.entity.player.PlayerEntity;
 import org.spongepowered.asm.mixin.Mixin;
 import org.spongepowered.asm.mixin.injection.At;
@@ -32,12 +35,18 @@ public class EntityRenderManagerMixin {
 
         if (!entity.isAlive() && ExtraOptions.hideDeadEntities) {
             cir.setReturnValue(false);
+            return;
         }
 
         if (entity instanceof ItemEntity item && Dungeons.highlightItems) {
             if (ItemHighlight.hideItem(item)) {
                 cir.setReturnValue(false);
+                return;
             }
         }
+
+        if (entity instanceof SheepEntity) {
+            if (Location.inDungeon() && MobHighlight.hideSheep) cir.setReturnValue(false);
+        }
     }
 }
diff --git a/src/main/java/blade/addon/utils/Misc.java b/src/main/java/blade/addon/utils/Misc.java
index 6d4e1e5..ea47dc8 100644
--- a/src/main/java/blade/addon/utils/Misc.java
+++ b/src/main/java/blade/addon/utils/Misc.java
@@ -14,6 +14,8 @@ import net.minecraft.entity.player.PlayerEntity;
 import net.minecraft.item.ItemStack;
 import net.minecraft.text.Text;
 import net.minecraft.util.Formatting;
+import net.minecraft.util.math.MathHelper;
+import net.minecraft.util.math.Vec3d;
 
 import java.util.List;
 
@@ -40,6 +42,13 @@ public class Misc {
         return clientPlayer.getName().getString().equals(name);
     }
 
+    public static Vec3d getPos(Entity entity, double tickProgress) {
+        double x = MathHelper.lerp(tickProgress, entity.lastRenderX, entity.getX());
+        double y = MathHelper.lerp(tickProgress, entity.lastRenderY, entity.getY());
+        double z = MathHelper.lerp(tickProgress, entity.lastRenderZ, entity.getZ());
+        return new Vec3d(x, y, z);
+    }
+
     public static double getDistance(Entity e1, Entity e2) {
         return getDistance(e1.getX(), e1.getZ(), e2.getX(), e2.getZ());
     }
diff --git a/src/main/java/blade/addon/utils/config/Config.java b/src/main/java/blade/addon/utils/config/Config.java
index 9044e83..4c49e60 100644
--- a/src/main/java/blade/addon/utils/config/Config.java
+++ b/src/main/java/blade/addon/utils/config/Config.java
@@ -189,7 +189,6 @@ public class Config {
         highlight.add(new ConfigDouble(Text.literal("Extra Wither Width"), () -> MobHighlight.witherExtraWidth, num -> MobHighlight.witherExtraWidth = num, 0.1, 0, 1.5));
         highlight.add(new ConfigInt(Text.literal("Outline Width"), () -> MobHighlight.outlineWidth, num -> MobHighlight.outlineWidth = num, 1, 1, 10));
         highlight.add(new ConfigOptions<>(Text.literal("Highlight mode"), MobHighlight.HighlightType.values(), () -> MobHighlight.currentHighlight, type -> MobHighlight.currentHighlight = type));
-        highlight.add(new ConfigBool(Text.literal("Highlight mimic chests"), () -> MobHighlight.highlightMimicChests, bool -> MobHighlight.highlightMimicChests = bool));
         highlight.add(new ConfigColor(Text.literal("Star mob filled color"), () -> MobHighlight.starFilledColor, color -> MobHighlight.starFilledColor = color, "star-filled", true));
         highlight.add(new ConfigColor(Text.literal("Star mob outline color"), () -> MobHighlight.starOutlineColor, color -> MobHighlight.starOutlineColor = color, "star-outline", true));
         highlight.add(new ConfigColor(Text.literal("Tank mob filled color"), () -> MobHighlight.tankFilledColor, color -> MobHighlight.tankFilledColor = color, "tank-filled", true));
@@ -202,10 +201,22 @@ public class Config {
         highlight.add(new ConfigColor(Text.literal("Shadow Assassin outline color"), () -> MobHighlight.assassinOutlineColor, color -> MobHighlight.assassinOutlineColor = color, "assassin-outline", true));
         highlight.add(new ConfigColor(Text.literal("Bat filled color"), () -> MobHighlight.batFilledColor, color -> MobHighlight.batFilledColor = color, "bat-filled", true));
         highlight.add(new ConfigColor(Text.literal("Bat outline color"), () -> MobHighlight.batOutlineColor, color -> MobHighlight.batOutlineColor = color, "bat-outline", true));
-        highlight.add(new ConfigColor(Text.literal("Wither color"), () -> MobHighlight.witherFilledColor, color -> MobHighlight.witherFilledColor = color, "wither-filled", true));
-        highlight.add(new ConfigColor(Text.literal("Wither outline"), () -> MobHighlight.witherOutlineColor, color -> MobHighlight.witherOutlineColor = color, "wither-outline", true));
-        highlight.add(new ConfigColor(Text.literal("Mimic color"), () -> MobHighlight.mimicFilledColor, color -> MobHighlight.mimicFilledColor = color, "mimic-filled", true));
-        highlight.add(new ConfigColor(Text.literal("Mimic outline"), () -> MobHighlight.mimicOutlineColor, color -> MobHighlight.mimicOutlineColor = color, "mimic-outline", true));
+        highlight.add(new ConfigColor(Text.literal("Wither filled color"), () -> MobHighlight.witherFilledColor, color -> MobHighlight.witherFilledColor = color, "wither-filled", true));
+        highlight.add(new ConfigColor(Text.literal("Wither outline color"), () -> MobHighlight.witherOutlineColor, color -> MobHighlight.witherOutlineColor = color, "wither-outline", true));
+
+        ConfigSection mimic = new ConfigSection(Text.literal("Mimic"));
+        mimic.add(new ConfigBool(Text.literal("Highlight mimic chests"), () -> MobHighlight.highlightMimicChests, bool -> MobHighlight.highlightMimicChests = bool));
+        mimic.add(new ConfigColor(Text.literal("Mimic filled color"), () -> MobHighlight.mimicFilledColor, color -> MobHighlight.mimicFilledColor = color, "mimic-filled", true));
+        mimic.add(new ConfigColor(Text.literal("Mimic outline color"), () -> MobHighlight.mimicOutlineColor, color -> MobHighlight.mimicOutlineColor = color, "mimic-outline", true));
+        highlight.add(mimic);
+
+        ConfigSection sheep = new ConfigSection(Text.literal("Sheep"));
+        sheep.add(new ConfigBool(Text.literal("Highlight sheep in dungeon"), () -> MobHighlight.highlightSheep, bool -> MobHighlight.highlightSheep = bool));
+        sheep.add(new ConfigBool(Text.literal("Hide sheep in dungeons"), () -> MobHighlight.hideSheep, bool -> MobHighlight.hideSheep = bool));
+        sheep.add(new ConfigColor(Text.literal("Sheep filled color"), () -> MobHighlight.sheepFilledColor, color -> MobHighlight.sheepFilledColor = color, "sheep-filled", true));
+        sheep.add(new ConfigColor(Text.literal("Sheep outline color"), () -> MobHighlight.sheepOutlineColor, color -> MobHighlight.sheepOutlineColor = color, "sheep-outline", true));
+        highlight.add(sheep);
+
         screen.addCategory(highlight);
 
         ConfigCategory extra = new ConfigCategory("Extra options");
diff --git a/src/main/java/blade/addon/utils/dungeon/Section.java b/src/main/java/blade/addon/utils/dungeon/Section.java
index 78ddabb..61cbf40 100644
--- a/src/main/java/blade/addon/utils/dungeon/Section.java
+++ b/src/main/java/blade/addon/utils/dungeon/Section.java
@@ -16,6 +16,7 @@ import net.minecraft.client.gui.DrawContext;
 import net.minecraft.text.Text;
 import net.minecraft.util.Formatting;
 
+import java.util.List;
 import java.util.regex.Matcher;
 import java.util.regex.Pattern;
 
@@ -184,8 +185,13 @@ public class Section {
             }
 
             if (Floor7.terminalTimeStamps) {
-                Misc.addChatMessage(Text.literal(name + " §a" + action + " " + objective + "! (§c" + currentCompleted + "§a/ " + totalNeeded + ") §8(§7" + getSectionTime() + "s §8| §7" + Phase.getPhaseTime(TERM_PHASE_INDEX) + "s§8)"));
-                return true;
+                //have to do it like this because for some reason they have the color in the
+                //Style object and not in the string literal
+                List<Text> texts = message.getSiblings();
+                if (!texts.isEmpty()) {
+                    Misc.addChatMessage(Text.literal(name).setStyle(texts.getFirst().getStyle()).append(Text.literal(" §a" + action + " " + objective + "! (§c" + currentCompleted + "§a/" + totalNeeded + ") §8(§7" + getSectionTime() + "s §8| §7" + Phase.getPhaseTime(TERM_PHASE_INDEX) + "s§8)")));
+                    return true;
+                }
             }
 
 
@@ -231,8 +237,9 @@ public class Section {
     }
 
     public static double getSectionTime() {
-        if (currentSection < 0 || currentSection >= splits.length) return -1;
-        return splits[currentSection].getRealTime();
+        int index = currentSection - 1;
+        if (index < 0 || index >= splits.length) return -1;
+        return splits[index].getRealTime();
     }
 
     public static boolean display() {
diff --git a/src/main/java/blade/addon/utils/dungeon/Split.java b/src/main/java/blade/addon/utils/dungeon/Split.java
index 6288dd8..7b7b12f 100644
--- a/src/main/java/blade/addon/utils/dungeon/Split.java
+++ b/src/main/java/blade/addon/utils/dungeon/Split.java
@@ -159,9 +159,9 @@ public class Split {
         if (timerType == TimerType.DIFFRENCE) {
             double diff = realTime - tickTime;
             if (diff > 0) {
-                serverTime = "+" + Constants.DECIMAL_FORMAT.format(diff) + "s ";
+                serverTime = "+" + Constants.DECIMAL_FORMAT.format(diff) + "s";
             } else {
-                serverTime = Constants.DECIMAL_FORMAT.format(diff) + "s ";
+                serverTime = Constants.DECIMAL_FORMAT.format(diff) + "s";
             }
         } else {
             //default to tick timer
diff --git a/src/main/java/blade/addon/utils/rendering/RenderUtils.java b/src/main/java/blade/addon/utils/rendering/RenderUtils.java
index e3e429f..ea75fe0 100644
--- a/src/main/java/blade/addon/utils/rendering/RenderUtils.java
+++ b/src/main/java/blade/addon/utils/rendering/RenderUtils.java
@@ -45,5 +45,4 @@ public class RenderUtils {
 
         RenderUtils.drawCenteredText(context, MinecraftClient.getInstance().textRenderer, Text.literal(Constants.DECIMAL_FORMAT.format(num)), x, y, component.getWidth(), color);
     }
-
 }
diff --git a/src/main/resources/blade-addons.mixins.json b/src/main/resources/blade-addons.mixins.json
index 56d8bbe..d50871a 100644
--- a/src/main/resources/blade-addons.mixins.json
+++ b/src/main/resources/blade-addons.mixins.json
@@ -3,9 +3,9 @@
   "package": "blade.addon.mixin",
   "compatibilityLevel": "JAVA_21",
   "mixins": [
+    "ChunkMixin",
     "ClientConnectionMixin",
     "ClientPlayNetworkHandlerMixin",
-    "ChunkMixin",
     "EntityMixin",
     "ItemStackMixin",
     "PlayerEntityMixin",
Updated SelectPet.java
Fixed regex for certain skins with levels, e.g Golden Dragon with Ancient skin.
athenameow · 2026-01-02 · d190e17
查看变更
src/main/java/blade/addon/features/other/SelectedPet.java | 6 +++---
 1 file changed, 3 insertions(+), 3 deletions(-)
diff --git a/src/main/java/blade/addon/features/other/SelectedPet.java b/src/main/java/blade/addon/features/other/SelectedPet.java
index e57a2d5..37fd707 100644
--- a/src/main/java/blade/addon/features/other/SelectedPet.java
+++ b/src/main/java/blade/addon/features/other/SelectedPet.java
@@ -22,9 +22,9 @@ public class SelectedPet {
 
     private static final int COLOR_OFFSET = 2;
 
-    private static final Pattern TAB_PET_PATTERN = Pattern.compile(" \\[Lvl \\d+] (\\D+)");
-    private static final Pattern MANUAL_EQUIP_PET_PATTERN = Pattern.compile("^You (summoned|despawned) your (\\D+)!$");
-    private static final Pattern RULE_EQUIP_PET_PATTERN = Pattern.compile("^Autopet equipped your \\[Lvl [0-9]+] (\\D+)! VIEW RULE$");
+    private static final Pattern TAB_PET_PATTERN = Pattern.compile(" \\[Lvl \\d+] (?:\\[[^\\]]+\\] )?(.+)");
+    private static final Pattern MANUAL_EQUIP_PET_PATTERN = Pattern.compile("^You (summoned|despawned) your (?:\\[Lvl \\d+] )?(?:\\[[^\\]]+\\] )?(.+)!$");
+    private static final Pattern RULE_EQUIP_PET_PATTERN = Pattern.compile("^Autopet equipped your \\[Lvl \\d+] (?:\\[[^\\]]+\\] )?(.+)! VIEW RULE$");
 
     private static final Text NO_PET = Text.literal("§cNo pet");
     private static final String identifierPrefix = "pets/";
a bunch of refactoring mostly for eventsGabriel Ellnebrand · 2026-01-01 · 489cd3e
查看变更
src/main/java/blade/addon/Bladeaddons.java                                          |  20 +++++-----
 src/main/java/blade/addon/features/dungeon/AutoRequeue.java                         |  14 +++----
 src/main/java/blade/addon/features/dungeon/ChestCounter.java                        |  21 ++++++-----
 src/main/java/blade/addon/features/dungeon/DeathTickTimer.java                      |  61 ------------------------------
 src/main/java/blade/addon/features/dungeon/DupeClassChecker.java                    |  85 -----------------------------------------
 src/main/java/blade/addon/features/dungeon/HidePlayers.java                         |  28 +++++++-------
 src/main/java/blade/addon/features/dungeon/KeyNotifier.java                         |  48 +++++++----------------
 src/main/java/blade/addon/features/dungeon/LeapMessage.java                         |  24 ++----------
 src/main/java/blade/addon/features/dungeon/RunStartValidator.java                   | 140 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 src/main/java/blade/addon/features/dungeon/SecretSpawnTimer.java                    |  20 ++++------
 src/main/java/blade/addon/features/dungeon/WarpCooldown.java                        |   6 +--
 src/main/java/blade/addon/features/dungeon/f7/CrystalSpawn.java                     |  44 +++++++++-------------
 src/main/java/blade/addon/features/dungeon/f7/DistanceToLedge.java                  |   7 +---
 src/main/java/blade/addon/features/dungeon/f7/DragSpawnTimer.java                   |  32 ++++++----------
 src/main/java/blade/addon/features/dungeon/f7/PillarExplode.java                    |  24 ++++++------
 src/main/java/blade/addon/features/dungeon/f7/PredevTimer.java                      |   2 +
 src/main/java/blade/addon/features/dungeon/f7/RelicTimer.java                       |  25 ++++++------
 src/main/java/blade/addon/features/dungeon/f7/StormTickTimer.java                   |  32 ++++------------
 src/main/java/blade/addon/features/dungeon/f7/invincibility/InvincibilityTimer.java |  74 +++++++++---------------------------
 src/main/java/blade/addon/features/dungeon/f7/invincibility/MaskHighlight.java      |  15 +-------
 src/main/java/blade/addon/features/dungeon/f7/location/LocationNotifier.java        |  21 ++++-------
 src/main/java/blade/addon/features/dungeon/f7/location/PositionMessage.java         |  11 ++----
 src/main/java/blade/addon/features/dungeon/f7/location/PositionMessages.java        |   2 +
 src/main/java/blade/addon/features/dungeon/f7/terms/GoldorTickTimer.java            |  21 ++---------
 src/main/java/blade/addon/features/dungeon/f7/terms/MelodyWarning.java              |  32 +++++++---------
 src/main/java/blade/addon/features/dungeon/f7/terms/Pre4Notifier.java               |   6 ++-
 src/main/java/blade/addon/features/dungeon/f7/terms/TermStartTimer.java             |  10 ++---
 src/main/java/blade/addon/features/highlight/MimicHighlight.java                    |   6 ++-
 src/main/java/blade/addon/features/highlight/MobHighlight.java                      |   5 ++-
 src/main/java/blade/addon/features/item/ProtectItem.java                            |  22 ++---------
 src/main/java/blade/addon/features/other/CompactHoppity.java                        |   2 +-
 src/main/java/blade/addon/features/other/DianaNotifier.java                         |   6 ++-
 src/main/java/blade/addon/features/other/KickedTimer.java                           |   7 ++--
 src/main/java/blade/addon/features/other/PracticeSS.java                            |   5 ++-
 src/main/java/blade/addon/features/other/RagDisplay.java                            |  24 ++++--------
 src/main/java/blade/addon/features/other/SelectedPet.java                           |  24 +++++-------
 src/main/java/blade/addon/mixin/ClientConnectionMixin.java                          |  19 ++++------
 src/main/java/blade/addon/mixin/ClientPlayNetworkHandlerMixin.java                  |  29 +++-----------
 src/main/java/blade/addon/mixin/HeldItemRendererMixin.java                          |   2 +
 src/main/java/blade/addon/mixin/MinecraftClientMixin.java                           |   2 +-
 src/main/java/blade/addon/mixin/PlayerInventoryMixin.java                           |   4 +-
 src/main/java/blade/addon/utils/Constants.java                                      |   8 +++-
 src/main/java/blade/addon/utils/Keybinds.java                                       |  22 +++++++++++
 src/main/java/blade/addon/utils/Location.java                                       |   4 +-
 src/main/java/blade/addon/utils/Misc.java                                           |  30 +++++++++------
 src/main/java/blade/addon/utils/config/Config.java                                  |   5 ++-
 src/main/java/blade/addon/utils/config/components/CombinedScreenNotifications.java  |   8 ++--
 src/main/java/blade/addon/utils/config/components/Components.java                   |  20 ++++------
 src/main/java/blade/addon/utils/config/values/Dungeons.java                         |  18 ++++++---
 src/main/java/blade/addon/utils/config/values/Floor7.java                           |   3 ++
 src/main/java/blade/addon/utils/data/ItemUtil.java                                  |  36 ++++++++++++++++++
 src/main/java/blade/addon/utils/dungeon/DungeonClass.java                           |   4 +-
 src/main/java/blade/addon/utils/dungeon/Phase.java                                  |  37 +++++++++---------
 src/main/java/blade/addon/utils/dungeon/Section.java                                |  80 ++++++++++++++++++++++++---------------
 src/main/java/blade/addon/utils/events/CancelableEvent.java                         |   6 ---
 src/main/java/blade/addon/utils/events/CancelableEventHandler.java                  |  22 -----------
 src/main/java/blade/addon/utils/events/CustomEvents.java                            |  22 ++++++++---
 src/main/java/blade/addon/utils/events/EventHandler.java                            |  23 +++++++-----
 src/main/java/blade/addon/utils/events/Events.java                                  |   6 +--
 src/main/java/blade/addon/utils/events/interfaces/BlockEntityEvent.java             |   2 +-
 src/main/java/blade/addon/utils/events/interfaces/CancelableMessageEvent.java       |   8 ----
 src/main/java/blade/addon/utils/events/interfaces/EntityEvent.java                  |   2 +-
 src/main/java/blade/addon/utils/events/interfaces/GameMessageEvent.java             |   7 +---
 src/main/java/blade/addon/utils/events/interfaces/LeapEvent.java                    |   2 +-
 src/main/java/blade/addon/utils/events/interfaces/LocationChangeEvent.java          |   2 +-
 src/main/java/blade/addon/utils/events/interfaces/ParticleEvent.java                |   2 +-
 src/main/java/blade/addon/utils/events/interfaces/PartyMessageEvent.java            |   2 +-
 src/main/java/blade/addon/utils/events/interfaces/PetEvent.java                     |   2 +-
 src/main/java/blade/addon/utils/events/interfaces/PhaseEvent.java                   |   2 +-
 src/main/java/blade/addon/utils/events/interfaces/PlayerListEvent.java              |   2 +-
 src/main/java/blade/addon/utils/events/interfaces/RunEndEvent.java                  |   2 +-
 src/main/java/blade/addon/utils/events/interfaces/ScoreBoardEvent.java              |   2 +-
 src/main/java/blade/addon/utils/events/interfaces/SectionEvent.java                 |   2 +-
 src/main/java/blade/addon/utils/events/interfaces/ServerTickEvent.java              |   2 +-
 src/main/java/blade/addon/utils/events/interfaces/SlotChangeEvent.java              |   2 +-
 src/main/java/blade/addon/utils/events/interfaces/TerminalEvent.java                |   2 +-
 src/main/java/blade/addon/utils/rendering/RenderUtils.java                          |  16 +++++++-
 src/main/java/blade/addon/utils/times/PersonalBest.java                             |   4 +-
 src/main/resources/assets/blade-addons/lang/en_us.json                              |   3 ++
 79 files changed, 641 insertions(+), 763 deletions(-)
diff --git a/src/main/java/blade/addon/Bladeaddons.java b/src/main/java/blade/addon/Bladeaddons.java
index 27e3dc8..056e6d9 100644
--- a/src/main/java/blade/addon/Bladeaddons.java
+++ b/src/main/java/blade/addon/Bladeaddons.java
@@ -2,8 +2,7 @@ package blade.addon;
 
 import blade.addon.features.dungeon.AutoRequeue;
 import blade.addon.features.dungeon.ChestCounter;
-import blade.addon.features.dungeon.DeathTickTimer;
-import blade.addon.features.dungeon.DupeClassChecker;
+import blade.addon.features.dungeon.RunStartValidator;
 import blade.addon.features.dungeon.ExplosiveShot;
 import blade.addon.features.dungeon.HidePlayers;
 import blade.addon.features.dungeon.ItemHighlight;
@@ -15,17 +14,17 @@ import blade.addon.features.dungeon.f7.BossWaypoints;
 import blade.addon.features.dungeon.f7.CrystalSpawn;
 import blade.addon.features.dungeon.f7.DistanceToLedge;
 import blade.addon.features.dungeon.f7.DragSpawnTimer;
-import blade.addon.features.dungeon.f7.terms.GoldorTickTimer;
-import blade.addon.features.dungeon.f7.invincibility.InvincibilityTimer;
-import blade.addon.features.dungeon.f7.terms.LeapNotification;
-import blade.addon.features.dungeon.f7.location.LocationNotifier;
-import blade.addon.features.dungeon.f7.terms.MelodyWarning;
 import blade.addon.features.dungeon.f7.PillarExplode;
-import blade.addon.features.dungeon.f7.location.PositionMessages;
-import blade.addon.features.dungeon.f7.terms.Pre4Notifier;
 import blade.addon.features.dungeon.f7.PredevTimer;
 import blade.addon.features.dungeon.f7.RelicTimer;
 import blade.addon.features.dungeon.f7.StormTickTimer;
+import blade.addon.features.dungeon.f7.invincibility.InvincibilityTimer;
+import blade.addon.features.dungeon.f7.location.LocationNotifier;
+import blade.addon.features.dungeon.f7.location.PositionMessages;
+import blade.addon.features.dungeon.f7.terms.GoldorTickTimer;
+import blade.addon.features.dungeon.f7.terms.LeapNotification;
+import blade.addon.features.dungeon.f7.terms.MelodyWarning;
+import blade.addon.features.dungeon.f7.terms.Pre4Notifier;
 import blade.addon.features.dungeon.f7.terms.TermStartTimer;
 import blade.addon.features.highlight.MimicHighlight;
 import blade.addon.features.highlight.MobHighlight;
@@ -69,13 +68,12 @@ public class Bladeaddons implements ModInitializer {
         StormTickTimer.init();
         GoldorTickTimer.init();
         TermStartTimer.init();
-        DeathTickTimer.init();
         PositionMessages.init();
         InvincibilityTimer.init();
         WarpCooldown.init();
         LeapMessage.init();
         ExplosiveShot.init();
-        DupeClassChecker.init();
+        RunStartValidator.init();
         HidePlayers.init();
         CrystalSpawn.init();
         KeyNotifier.init();
diff --git a/src/main/java/blade/addon/features/dungeon/AutoRequeue.java b/src/main/java/blade/addon/features/dungeon/AutoRequeue.java
index c138439..e22ba9b 100644
--- a/src/main/java/blade/addon/features/dungeon/AutoRequeue.java
+++ b/src/main/java/blade/addon/features/dungeon/AutoRequeue.java
@@ -13,8 +13,6 @@ import java.util.regex.Pattern;
 
 public class AutoRequeue {
 
-    private static final Pattern DT_PATTERN = Pattern.compile("!dt");
-    private static final Pattern UNDT_PATTERN = Pattern.compile("!undt");
     private static final Pattern LEFT_PATTERN = Pattern.compile("has left the party.$");
 
     private static final HashMap<String, String> playerHashMap = new HashMap<>();
@@ -25,19 +23,19 @@ public class AutoRequeue {
     public static void init() {
         Events.ON_PARTY_MESSAGE.register((username, message) -> {
             if (playerHashMap.containsKey(username)) {
-                Matcher matcher = UNDT_PATTERN.matcher(message);
-                if (matcher.find()) {
+                if (message.contains("!undt")) {
                     playerHashMap.remove(username);
                     Misc.addChatMessage(Text.literal("§aDowntime removed"));
                 }
             } else {
-                Matcher matcher = DT_PATTERN.matcher(message);
-                if (matcher.find()) {
+                if (message.contains("!dt")) {
                     playerHashMap.put(username, message);
                     Misc.addChatMessage(Text.literal("§aDowntime added"));
                 }
             }
+            return false;
         });
+
         ClientReceiveMessageEvents.GAME.register((message, overlay) -> {
             if (!Location.inDungeon() && !Dungeons.enableAutoRequeue) return;
             String string = message.getString();
@@ -48,7 +46,7 @@ public class AutoRequeue {
         });
 
         Events.ON_RUN_END.register(() -> {
-            if (!Dungeons.enableAutoRequeue || someoneLeft) return;
+            if (!Dungeons.enableAutoRequeue || someoneLeft) return false;
 
             if (playerHashMap.isEmpty()) {
                 Misc.executeCommand("instancerequeue");
@@ -58,11 +56,13 @@ public class AutoRequeue {
 
 
             }
+            return false;
         });
 
         Events.ON_LOCATION_CHANGE.register(newLocation -> {
             playerHashMap.clear();
             someoneLeft = false;
+            return false;
         });
     }
 }
diff --git a/src/main/java/blade/addon/features/dungeon/ChestCounter.java b/src/main/java/blade/addon/features/dungeon/ChestCounter.java
index d9b4828..27f9801 100644
--- a/src/main/java/blade/addon/features/dungeon/ChestCounter.java
+++ b/src/main/java/blade/addon/features/dungeon/ChestCounter.java
@@ -1,13 +1,15 @@
 package blade.addon.features.dungeon;
 
+import blade.addon.utils.Constants;
 import blade.addon.utils.Location;
+import blade.addon.utils.Misc;
+import blade.addon.utils.Scheduler;
 import blade.addon.utils.config.values.Dungeons;
 import blade.addon.utils.dungeon.Phase;
 import blade.addon.utils.events.Events;
 import config.practical.hud.HUDComponent;
 import net.minecraft.client.MinecraftClient;
 import net.minecraft.client.gui.DrawContext;
-import net.minecraft.client.network.ClientPlayerEntity;
 import net.minecraft.sound.SoundEvents;
 import net.minecraft.text.Text;
 
@@ -27,12 +29,13 @@ public class ChestCounter {
             if (Location.in(Location.DUNGEON_HUB)) {
                 hasUpdatedData = false;
             }
+            return false;
         });
 
         Events.ON_PLAYER_ENTRY.register(receivedEntry -> {
-            if (receivedEntry == null || !Location.in(Location.DUNGEON_HUB)) return;
+            if (receivedEntry == null || !Location.in(Location.DUNGEON_HUB)) return false;
             Text text = receivedEntry.displayName();
-            if (text == null) return;
+            if (text == null) return false;
 
             String string = text.getString();
             Matcher matcher = PATTERN.matcher(string);
@@ -44,17 +47,17 @@ public class ChestCounter {
                 } catch (NumberFormatException ignored) {
                 }
             }
+            return false;
         });
 
         Events.ON_RUN_END.register(() -> {
             countedChests++;
             if (Dungeons.sendChestWarning && chestDisplayCount + countedChests >= Dungeons.chestWarningCount) {
-                ClientPlayerEntity player = MinecraftClient.getInstance().player;
-                if (player != null) {
-                    player.playSound(SoundEvents.BLOCK_NOTE_BLOCK_PLING.value(), 2, 1);
-                }
-                MinecraftClient.getInstance().inGameHud.setTitle(Text.literal("Chest count reached").withColor(0xffff0000));
+                Scheduler.scheduleSound(SoundEvents.BLOCK_NOTE_BLOCK_PLING.value(), 2, 1);
+                Misc.setTitle(Text.literal("§cChest count reached"));
             }
+
+            return false;
         });
     }
 
@@ -70,7 +73,7 @@ public class ChestCounter {
         if (hasUpdatedData) {
             context.drawText(MinecraftClient.getInstance().textRenderer, Text.literal("Chests: " + Math.min(chestDisplayCount + countedChests, 60)), x, y, Dungeons.chestCountColor , true);
         } else {
-            context.drawText(MinecraftClient.getInstance().textRenderer, Text.literal("go to the dungeon hub"), x, y, 0xffff0000, true);
+            context.drawText(MinecraftClient.getInstance().textRenderer, Text.literal("go to the dungeon hub"), x, y, Constants.RED_COLOR, true);
 
         }
     }
diff --git a/src/main/java/blade/addon/features/dungeon/DeathTickTimer.java b/src/main/java/blade/addon/features/dungeon/DeathTickTimer.java
deleted file mode 100644
index e2e1adf..0000000
--- a/src/main/java/blade/addon/features/dungeon/DeathTickTimer.java
+++ /dev/null
@@ -1,61 +0,0 @@
-package blade.addon.features.dungeon;
-
-import blade.addon.utils.Constants;
-import blade.addon.utils.Location;
-import blade.addon.utils.config.values.Dungeons;
-import blade.addon.utils.events.Events;
-import blade.addon.utils.rendering.RenderUtils;
-import config.practical.hud.HUDComponent;
-import net.minecraft.client.MinecraftClient;
-import net.minecraft.client.gui.DrawContext;
-import net.minecraft.text.Text;
-import net.minecraft.util.math.Vec3d;
-
-public class DeathTickTimer {
-
-    private static final int TOTAL_DEATH_TICKS = 40;
-    private static Vec3d firstPosition;
-
-    private static int tick = TOTAL_DEATH_TICKS;
-
-    public static void init() {
-        Events.ON_SERVER_TICK.register(() -> {
-            if (Location.inDungeon()) {
-                tick--;
-                if (tick <= 0) tick = TOTAL_DEATH_TICKS;
-            }
-        });
-
-        Events.ON_LOCATION_CHANGE.register(newLocation -> {
-            if (Location.inDungeon()) {
-                tick = 0;
-                firstPosition = null;
-            }
-        });
-    }
-
-    public static void onTeleport(Vec3d position) {
-        if (Location.inDungeon()) {
-            if (firstPosition == null) {
-                firstPosition = position;
-            }
-
-            else if (position.x == firstPosition.x && position.y == firstPosition.y && position.z == firstPosition.z) {
-                tick = TOTAL_DEATH_TICKS;
-            }
-        }
-    }
-
-    public static boolean display() {
-        return Dungeons.enableDeathTickTimer && Location.inDungeon();
-    }
-
-    public static void render(HUDComponent component, DrawContext context) {
-        int x = component.getScaledX();
-        int y = component.getScaledY();
-
-        double num = tick * Constants.TICK_DURATION;
-        RenderUtils.drawCenteredText(context, MinecraftClient.getInstance().textRenderer, Text.literal(Constants.DECIMAL_FORMAT.format(num)), x, y, component.getWidth());
-
-    }
-}
diff --git a/src/main/java/blade/addon/features/dungeon/DupeClassChecker.java b/src/main/java/blade/addon/features/dungeon/DupeClassChecker.java
deleted file mode 100644
index 74fecf7..0000000
--- a/src/main/java/blade/addon/features/dungeon/DupeClassChecker.java
+++ /dev/null
@@ -1,85 +0,0 @@
-package blade.addon.features.dungeon;
-
-import blade.addon.utils.Location;
-import blade.addon.utils.config.values.Dungeons;
-import blade.addon.utils.dungeon.Phase;
-import blade.addon.utils.events.Events;
-import config.practical.hud.HUDComponent;
-import net.fabricmc.fabric.api.client.message.v1.ClientReceiveMessageEvents;
-import net.minecraft.client.MinecraftClient;
-import net.minecraft.client.gui.DrawContext;
-import net.minecraft.client.network.ClientPlayerEntity;
-import net.minecraft.client.world.ClientWorld;
-import net.minecraft.scoreboard.Scoreboard;
-import net.minecraft.scoreboard.Team;
-import net.minecraft.sound.SoundEvents;
-import net.minecraft.text.Text;
-import net.minecraft.util.Formatting;
-
-import java.util.HashSet;
-import java.util.regex.Matcher;
-import java.util.regex.Pattern;
-
-public class DupeClassChecker {
-
-    private static final Pattern STARTING_PATTERN = Pattern.compile("^Starting in \\d second(s)?");
-    private static final Pattern CLASS_PATTERN = Pattern.compile("\\[[ABHMT]]");
-
-    private static final Text DISPLAY_TEXT = Text.literal("Duplicate Class Detected").formatted(Formatting.RED);
-
-    private static boolean hasDuplicateClass = false;
-
-    public static void init() {
-        ClientReceiveMessageEvents.GAME.register((message, overlay) -> {
-            if (!Location.inDungeon() || Phase.runStarted() || !Dungeons.detectDuplicateClass) return;
-
-            Matcher matcher = STARTING_PATTERN.matcher(message.getString());
-            if (!matcher.find()) return;
-
-            hasDuplicateClass = hasDuplicateClasses();
-        });
-
-        Events.ON_LOCATION_CHANGE.register(location -> {
-            if (Location.inDungeon()) {
-                hasDuplicateClass = false;
-            }
-        });
-    }
-
-    private static boolean hasDuplicateClasses() {
-
-        ClientWorld world = MinecraftClient.getInstance().world;
-        ClientPlayerEntity player = MinecraftClient.getInstance().player;
-        if (world == null || player == null) return false;
-
-        Scoreboard scoreboard = world.getScoreboard();
-        HashSet<String> set = new HashSet<>();
-
-        for (Team team : scoreboard.getTeams()) {
-            String teamStr = team.getPrefix().getString() + team.getSuffix().getString();
-            Matcher matcher = CLASS_PATTERN.matcher(teamStr);
-            if (matcher.find()) {
-                String dungeonClass = matcher.group();
-
-                if (set.contains(dungeonClass)) {
-                    player.playSound(SoundEvents.BLOCK_NOTE_BLOCK_PLING.value(), 2, 1);
-                    return true;
-                }
-
-                set.add(dungeonClass);
-            }
-        }
-        return false;
-    }
-
-    public static boolean display() {
-        return  Location.inDungeon() && hasDuplicateClass && !Phase.runStarted();
-    }
-
-    public static void render(HUDComponent component, DrawContext context) {
-        int x = component.getScaledX();
-        int y = component.getScaledY();
-
-        context.drawText(MinecraftClient.getInstance().textRenderer, DISPLAY_TEXT, x, y, 0xffffffff, true);
-    }
-}
diff --git a/src/main/java/blade/addon/features/dungeon/HidePlayers.java b/src/main/java/blade/addon/features/dungeon/HidePlayers.java
index 518b023..e9beacc 100644
--- a/src/main/java/blade/addon/features/dungeon/HidePlayers.java
+++ b/src/main/java/blade/addon/features/dungeon/HidePlayers.java
@@ -26,6 +26,7 @@ public class HidePlayers {
         Events.ON_LEAP.register(message -> {
             justLeapt = true;
             startTime = System.currentTimeMillis();
+            return false;
         });
     }
 
@@ -48,11 +49,19 @@ public class HidePlayers {
         return SS_POSITION.distanceTo(player.getEntityPos()) <= DISTANCE;
     }
 
-    public static boolean shouldHidePlayers(PlayerEntity player) {
-        if (!Location.inDungeon()) return false;
+    public static boolean testHideInRange(PlayerEntity player) {
+        if (!Dungeons.hidePlayersInRange) return false;
+        ClientPlayerEntity clientPlayer = MinecraftClient.getInstance().player;
+        if (clientPlayer == null) return false;
 
-        if (!Detection.isARealPlayer(player)) return false;
-        if (Misc.isClientPlayer(player)) return false;
+        double distance = player.getEntityPos().distanceTo(clientPlayer.getEntityPos());
+        if (!Double.isFinite(distance) || !Double.isFinite(Dungeons.hidePlayerRange)) return false;
+        return distance <= Dungeons.hidePlayerRange;
+
+    }
+
+    public static boolean shouldHidePlayers(PlayerEntity player) {
+        if (!Location.inDungeon() || !Detection.isARealPlayer(player) || Misc.isClientPlayer(player)) return false;
 
         if (testHideAtLeap()) {
             return true;
@@ -62,16 +71,7 @@ public class HidePlayers {
             return true;
         }
 
-        if (Dungeons.hidePlayersInRange) {
-            ClientPlayerEntity clientPlayer = MinecraftClient.getInstance().player;
-            if (clientPlayer == null) return false;
-
-            double distance = player.getEntityPos().distanceTo(clientPlayer.getEntityPos());
-            if (!Double.isFinite(distance) || !Double.isFinite(Dungeons.hidePlayerRange)) return false;
-            return distance <= Dungeons.hidePlayerRange;
-        }
-
-        return false;
+        return testHideInRange(player);
     }
 
 
diff --git a/src/main/java/blade/addon/features/dungeon/KeyNotifier.java b/src/main/java/blade/addon/features/dungeon/KeyNotifier.java
index 7d60d79..edb4930 100644
--- a/src/main/java/blade/addon/features/dungeon/KeyNotifier.java
+++ b/src/main/java/blade/addon/features/dungeon/KeyNotifier.java
@@ -1,27 +1,24 @@
 package blade.addon.features.dungeon;
 
 import blade.addon.utils.Location;
+import blade.addon.utils.Scheduler;
 import blade.addon.utils.config.values.Dungeons;
 import blade.addon.utils.dungeon.DungeonClass;
 import blade.addon.utils.events.Events;
 import config.practical.hud.HUDComponent;
-import net.fabricmc.fabric.api.client.event.lifecycle.v1.ClientTickEvents;
 import net.fabricmc.fabric.api.client.message.v1.ClientReceiveMessageEvents;
 import net.minecraft.client.MinecraftClient;
 import net.minecraft.client.gui.DrawContext;
-import net.minecraft.client.network.ClientPlayerEntity;
 import net.minecraft.sound.SoundEvents;
-import net.minecraft.text.Style;
 import net.minecraft.text.Text;
-import net.minecraft.util.Formatting;
 
 import java.util.regex.Matcher;
 import java.util.regex.Pattern;
 
 public class KeyNotifier {
 
-    private final static Text BLOOD_KEY = Text.literal("Blood Key Dropped").setStyle(Style.EMPTY.withBold(true).withColor(Formatting.RED));
-    private final static Text WITHER_KEY = Text.literal("Wither Key Dropped").setStyle(Style.EMPTY.withBold(true).withColor(Formatting.GRAY));
+    private final static Text BLOOD_KEY = Text.literal("§l§cBlood Key Dropped");
+    private final static Text WITHER_KEY = Text.literal("§l§7Wither Key Dropped");
 
     private final static Pattern KEY_ENTITY_PATTERN = Pattern.compile("(Wither|Blood) Key");
     private final static Pattern AUTOMATIC_PICKUP_PATTERN = Pattern.compile("A (Wither|Blood) Key was picked up!");
@@ -29,27 +26,24 @@ public class KeyNotifier {
 
     private static boolean hasKey = false;
     private static boolean isBloodKey = false;
-    private static boolean sentSound = false;
 
     public static void init() {
         Events.ON_ENTITY_TRACKED.register((entity, world) -> {
-            if (!Location.inDungeon() || hasKey || !Dungeons.enableKeyNotifier) return;
-            if (!DungeonClass.isClass(DungeonClass.ARCHER) && !DungeonClass.isClass(DungeonClass.MAGE)) return;
+            if (!Location.inDungeon() || hasKey || !Dungeons.enableKeyNotifier) return false;
+            if (!DungeonClass.isClass(DungeonClass.ARCHER) && !DungeonClass.isClass(DungeonClass.MAGE)) return false;
 
 
             Text text = entity.getCustomName();
-            if (text == null) return;
+            if (text == null) return false;
 
             Matcher matcher = KEY_ENTITY_PATTERN.matcher(text.getString());
             if (matcher.find()) {
-                String str = matcher.group(1);
-
-                if (str.contains("Blood")) {
-                    isBloodKey = true;
-                }
-
+                isBloodKey = matcher.group(1).matches("Blood");
                 hasKey = true;
+                Scheduler.scheduleSound(SoundEvents.ENTITY_EXPERIENCE_ORB_PICKUP, 2, 0.5f);
             }
+
+            return false;
         });
 
         ClientReceiveMessageEvents.GAME.register((message, overlay) -> {
@@ -60,7 +54,6 @@ public class KeyNotifier {
             if (matcher.find()) {
                 hasKey = false;
                 isBloodKey = false;
-                sentSound = false;
                 return;
             }
 
@@ -68,7 +61,6 @@ public class KeyNotifier {
             if (matcher.find()) {
                 hasKey = false;
                 isBloodKey = false;
-                sentSound = false;
             }
         });
 
@@ -76,19 +68,9 @@ public class KeyNotifier {
             if (Location.inDungeon()) {
                 isBloodKey = false;
                 hasKey = false;
-                sentSound = false;
             }
+            return false;
         });
-
-        ClientTickEvents.END_CLIENT_TICK.register((client) -> {
-            if (hasKey && !sentSound) {
-                ClientPlayerEntity player = client.player;
-                if (player == null) return;
-                player.playSound(SoundEvents.ENTITY_EXPERIENCE_ORB_PICKUP, 2, 0.5f);
-                sentSound = true;
-            }
-        });
-
     }
 
     public static boolean display() {
@@ -99,11 +81,7 @@ public class KeyNotifier {
         int x = component.getScaledX();
         int y = component.getScaledY();
 
-        if (isBloodKey) {
-            context.drawText(MinecraftClient.getInstance().textRenderer, BLOOD_KEY, x, y, 0xffffffff, true);
-        } else {
-            context.drawText(MinecraftClient.getInstance().textRenderer, WITHER_KEY, x, y, 0xffffffff, true);
-        }
-
+        Text text = isBloodKey ? BLOOD_KEY : WITHER_KEY;
+        context.drawText(MinecraftClient.getInstance().textRenderer, text, x, y, 0xffffffff, true);
     }
 }
diff --git a/src/main/java/blade/addon/features/dungeon/LeapMessage.java b/src/main/java/blade/addon/features/dungeon/LeapMessage.java
index 6e0c92f..1e3fa56 100644
--- a/src/main/java/blade/addon/features/dungeon/LeapMessage.java
+++ b/src/main/java/blade/addon/features/dungeon/LeapMessage.java
@@ -1,40 +1,22 @@
 package blade.addon.features.dungeon;
 
-import blade.addon.utils.Location;
 import blade.addon.utils.config.values.Dungeons;
 import blade.addon.utils.events.Events;
-import net.fabricmc.fabric.api.client.message.v1.ClientReceiveMessageEvents;
 import net.minecraft.client.MinecraftClient;
 import net.minecraft.client.network.ClientPlayNetworkHandler;
 
-import java.util.regex.Matcher;
-import java.util.regex.Pattern;
-
 public class LeapMessage {
 
     private static final int INDEX_REMOVAL = 9;
-    private static final Pattern SEARCH_PATTERN = Pattern.compile("^You have teleported to .*!$");
 
     public static void init() {
-        ClientReceiveMessageEvents.GAME.register((message, overlay) -> {
-            if (Location.inDungeon()) {
-                String string = message.getString();
-
-                Matcher matcher = SEARCH_PATTERN.matcher(string);
-                if (!matcher.find()) return;
-
-                if (Events.ON_LEAP.hasListeners()) {
-                    Events.ON_LEAP.listeners.forEach(leapEvent -> leapEvent.onLeap(message));
-                }
-            }
-        });
-
         Events.ON_LEAP.register(message -> {
-            if (!Dungeons.enableLeapMessages) return;
+            if (!Dungeons.enableLeapMessages) return false;
 
             ClientPlayNetworkHandler networkHandler = MinecraftClient.getInstance().getNetworkHandler();
-            if (networkHandler == null) return;
+            if (networkHandler == null) return false;
             networkHandler.sendChatCommand("pc " + message.getString().substring(INDEX_REMOVAL));
+            return false;
         });
     }
 
diff --git a/src/main/java/blade/addon/features/dungeon/RunStartValidator.java b/src/main/java/blade/addon/features/dungeon/RunStartValidator.java
new file mode 100644
index 0000000..a247ef7
--- /dev/null
+++ b/src/main/java/blade/addon/features/dungeon/RunStartValidator.java
@@ -0,0 +1,140 @@
+package blade.addon.features.dungeon;
+
+import blade.addon.utils.Location;
+import blade.addon.utils.Scheduler;
+import blade.addon.utils.config.values.Dungeons;
+import blade.addon.utils.dungeon.Phase;
+import blade.addon.utils.events.Events;
+import blade.addon.utils.rendering.RenderUtils;
+import config.practical.hud.HUDComponent;
+import net.fabricmc.fabric.api.client.message.v1.ClientReceiveMessageEvents;
+import net.minecraft.client.MinecraftClient;
+import net.minecraft.client.gui.DrawContext;
+import net.minecraft.client.network.ClientPlayerEntity;
+import net.minecraft.client.world.ClientWorld;
+import net.minecraft.scoreboard.Scoreboard;
+import net.minecraft.scoreboard.Team;
+import net.minecraft.sound.SoundEvents;
+import net.minecraft.text.Text;
+
+import java.util.HashMap;
+import java.util.Map;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+public class RunStartValidator {
+
+    private static final Pattern STARTING_PATTERN = Pattern.compile("^Starting in \\d second(s)?");
+    private static final Pattern CLASS_PATTERN = Pattern.compile("\\[([ABHMT])]");
+
+    private static final Text DUPE_CLASS_TEXT = Text.literal("§cDuplicate Class Detected");
+    private static final Text PLAYER_COUNT_TEXT = Text.literal("§cNot enough players");
+
+    private static boolean hasDupeClasses = false;
+    private static boolean notEnoughPlayers = false;
+
+    public static void init() {
+        ClientReceiveMessageEvents.GAME.register((message, overlay) -> {
+            if (!Location.inDungeon() || Phase.runStarted() || !Dungeons.detectDuplicateClass) return;
+
+            Matcher matcher = STARTING_PATTERN.matcher(message.getString());
+            if (!matcher.find()) return;
+
+            if (validate()) {
+                Scheduler.scheduleSound(SoundEvents.BLOCK_NOTE_BLOCK_PLING.value(), 2, 1);
+            }
+        });
+
+        Events.ON_LOCATION_CHANGE.register(location -> {
+            if (Location.inDungeon()) {
+                reset();
+            }
+
+            return false;
+        });
+
+        Events.ON_PLAYER_ENTRY.register(receivedEntry -> {
+            if (!Location.inDungeon() || Phase.runStarted() || (!Dungeons.detectDuplicateClass && !Dungeons.detectPlayerCount)) return false;
+            validate();
+            return false;
+        });
+    }
+
+    private static boolean validate() {
+        ClientWorld world = MinecraftClient.getInstance().world;
+        ClientPlayerEntity player = MinecraftClient.getInstance().player;
+        if (world == null || player == null) return false;
+
+        Scoreboard scoreboard = world.getScoreboard();
+        HashMap<String, Integer> map = readScoreBoard(scoreboard);
+
+        checkPlayerCount(map);
+        checkDuplicateClasses(map);
+        return hasDupeClasses || notEnoughPlayers;
+    }
+
+    private static void checkDuplicateClasses(HashMap<String, Integer> map) {
+        for (Map.Entry<String, Integer> entry : map.entrySet()) {
+            String clazz = entry.getKey();
+            Integer count = entry.getValue();
+            if (clazz.equals("M") && Dungeons.ignoreDupeMage) continue;
+
+            if (count > 1) {
+                hasDupeClasses = true;
+                return;
+            }
+        }
+
+        hasDupeClasses = false;
+    }
+
+    private static void checkPlayerCount(HashMap<String, Integer> map) {
+        if (!Dungeons.detectPlayerCount) {
+            notEnoughPlayers = false;
+            return;
+        }
+        int count = 0;
+        for (int num : map.values()) {
+            count += num;
+        }
+
+        notEnoughPlayers = count < Dungeons.playersNeeded;
+    }
+
+    private static void reset() {
+        hasDupeClasses = false;
+        notEnoughPlayers = false;
+    }
+
+    private static HashMap<String, Integer> readScoreBoard(Scoreboard scoreboard) {
+        HashMap<String, Integer> map = new HashMap<>();
+
+        for (Team team : scoreboard.getTeams()) {
+            String teamStr = team.getPrefix().getString() + team.getSuffix().getString();
+            Matcher matcher = CLASS_PATTERN.matcher(teamStr);
+            if (!matcher.find()) continue;
+
+            String clazz = matcher.group(1);
+            Integer count = map.get(clazz);
+            if (count == null) count = 0;
+            map.put(clazz, count + 1);
+        }
+        return map;
+    }
+
+    public static boolean display() {
+        return Location.inDungeon() && !Phase.runStarted() && (hasDupeClasses || notEnoughPlayers);
+    }
+
+    public static void render(HUDComponent component, DrawContext context) {
+        int x = component.getScaledX();
+        int y = component.getScaledY();
+        if (hasDupeClasses) {
+            RenderUtils.drawCenteredText(context, MinecraftClient.getInstance().textRenderer, DUPE_CLASS_TEXT, x, y, component.getWidth());
+        } else if (notEnoughPlayers) {
+            RenderUtils.drawCenteredText(context, MinecraftClient.getInstance().textRenderer, PLAYER_COUNT_TEXT, x, y, component.getWidth());
+        } else {
+            RenderUtils.drawCenteredText(context, MinecraftClient.getInstance().textRenderer, Text.literal("§cSome warning text"), x, y, component.getWidth());
+        }
+    }
+}
diff --git a/src/main/java/blade/addon/features/dungeon/SecretSpawnTimer.java b/src/main/java/blade/addon/features/dungeon/SecretSpawnTimer.java
index 819ab83..fb5e55b 100644
--- a/src/main/java/blade/addon/features/dungeon/SecretSpawnTimer.java
+++ b/src/main/java/blade/addon/features/dungeon/SecretSpawnTimer.java
@@ -24,33 +24,29 @@ public class SecretSpawnTimer {
     public static void init() {
 
         Events.ON_TEAM.register(text -> {
-            if (!Location.inDungeon() || !Dungeons.enableSecretSpawnTimer) return;
-
+            if (!Location.inDungeon() || !Dungeons.enableSecretSpawnTimer) return false;
             Matcher matcher = PATTERN.matcher(text);
             if (matcher.find()) {
                 tick = TICKS_PER_SECOND;
             }
+            return false;
         });
 
-        Events.ON_SERVER_TICK.register(() -> tick = Math.max(tick - 1, 0));
+        Events.ON_SERVER_TICK.register(() -> {
+            tick = Math.max(tick - 1, 0);
+            return false;
+        });
     }
 
     public static boolean display() {
-        return  Location.inDungeon() && Dungeons.enableSecretSpawnTimer && !Phase.inBoss() && Phase.runStarted();
+        return Location.inDungeon() && Dungeons.enableSecretSpawnTimer && !Phase.inBoss() && Phase.runStarted();
     }
 
     public static void render(HUDComponent component, DrawContext context) {
         int x = component.getScaledX();
         int y = component.getScaledY();
 
-        int color;
-        if (tick > 10) {
-            color = Constants.GREEN_COLOR;
-        } else if (tick > 5) {
-            color = Constants.ORANGE_COLOR;
-        } else {
-            color = Constants.RED_COLOR;
-        }
+        int color = tick > 10 ? Constants.GREEN_COLOR : tick > 5 ? Constants.ORANGE_COLOR : Constants.RED_COLOR;
 
         RenderUtils.drawCenteredText(context, MinecraftClient.getInstance().textRenderer, Text.literal(tick + ""), x, y, component.getWidth(), color);
 
diff --git a/src/main/java/blade/addon/features/dungeon/WarpCooldown.java b/src/main/java/blade/addon/features/dungeon/WarpCooldown.java
index b5fbb70..cf46007 100644
--- a/src/main/java/blade/addon/features/dungeon/WarpCooldown.java
+++ b/src/main/java/blade/addon/features/dungeon/WarpCooldown.java
@@ -7,7 +7,6 @@ import net.fabricmc.fabric.api.client.message.v1.ClientReceiveMessageEvents;
 import net.minecraft.client.MinecraftClient;
 import net.minecraft.client.gui.DrawContext;
 import net.minecraft.text.Text;
-import net.minecraft.util.Formatting;
 
 import java.util.regex.Matcher;
 import java.util.regex.Pattern;
@@ -48,9 +47,6 @@ public class WarpCooldown {
         int y = component.getScaledY();
 
         double timeRemaining = Math.max((endTime - System.currentTimeMillis()) / 1000.0, 0);
-
-        Text text = Text.literal("Warp Cooldown: ").formatted(Formatting.DARK_PURPLE).append(Text.literal(Constants.DECIMAL_FORMAT.format(timeRemaining)).formatted(Formatting.WHITE));
-
-        context.drawText(MinecraftClient.getInstance().textRenderer, text, x, y, 0xffffffff, true);
+        context.drawText(MinecraftClient.getInstance().textRenderer, Text.literal("§5Warp Cooldown: §f" + Constants.DECIMAL_FORMAT.format(timeRemaining)), x, y, 0xffffffff, true);
     }
 }
diff --git a/src/main/java/blade/addon/features/dungeon/f7/CrystalSpawn.java b/src/main/java/blade/addon/features/dungeon/f7/CrystalSpawn.java
index 1425848..94b84ec 100644
--- a/src/main/java/blade/addon/features/dungeon/f7/CrystalSpawn.java
+++ b/src/main/java/blade/addon/features/dungeon/f7/CrystalSpawn.java
@@ -14,7 +14,6 @@ import net.minecraft.client.gui.DrawContext;
 import net.minecraft.client.network.ClientPlayerEntity;
 import net.minecraft.entity.decoration.EndCrystalEntity;
 import net.minecraft.text.Text;
-import net.minecraft.util.Formatting;
 
 import java.util.regex.Matcher;
 import java.util.regex.Pattern;
@@ -34,18 +33,18 @@ public class CrystalSpawn {
     public static void init() {
 
         Events.ON_GAME_MESSAGE.register(text -> {
-            if (!Location.inDungeon() || !Phase.inP1() || !Floor7.enableCrystalSpawnTime) return;
+            if (!Location.inDungeon() || !Phase.inP1() || !Floor7.enableCrystalSpawnTime) return false;
 
             Matcher matcher = PATTERN_1.matcher(text.getString());
             if (matcher.find()) {
                 tick = TICK_SPAWN;
-                return;
+                return false;
             }
 
             matcher = PATTERN_2.matcher(text.getString());
             if (matcher.find()) {
                 tick = TICK_SPAWN;
-                return;
+                return false;
             }
 
             matcher = RELIC_PICK_UP.matcher(text.getString());
@@ -57,37 +56,35 @@ public class CrystalSpawn {
                 }
 
             }
-
+            return false;
+        });
+        Events.ON_SERVER_TICK.register(() -> {
+            tick = Math.max(tick - 1, 0);
+            return false;
         });
-        Events.ON_SERVER_TICK.register(() -> tick = Math.max(tick - 1, 0));
 
         Events.ON_LOCATION_CHANGE.register(newLocation -> {
-            if (Location.inDungeon()) {
-                tick = 0;
-                pickedUp = false;
-            }
+            tick = 0;
+            pickedUp = false;
+            return false;
         });
 
         Events.ON_ENTITY_SPAWNED.register((entity, world) -> {
-            if (!pickedUp || !Location.inDungeon() || !Floor7.enableCrystalSpawnTime || !Phase.inP1()) return;
+            if (!pickedUp || !Location.inDungeon() || !Floor7.enableCrystalSpawnTime || !Phase.inP1()) return false;
+
             if (entity instanceof EndCrystalEntity crystal) {
                 ClientPlayerEntity player = MinecraftClient.getInstance().player;
-                if (player == null) return;
+                if (player == null) return false;
 
-                double px = player.getX();
-                double cx = crystal.getX();
-                double pz = player.getZ();
-                double cz = crystal.getZ();
+                double distance = Misc.getDistance(player, crystal);
 
-                double distance = Misc.getDistance(px, pz, cx, cz);
-
-                if (distance < 5 && crystal.getY() == 224.375) {
+                if (distance < 6 && crystal.getY() == 224.375) {
                     PersonalBests.crystalTime.testNewTime(Text.literal("§aCrystal placed in "), pickupTime);
                     pickedUp = false;
                 }
 
             }
-
+            return false;
         });
     }
 
@@ -96,11 +93,6 @@ public class CrystalSpawn {
     }
 
     public static void render(HUDComponent component, DrawContext context) {
-        int x = component.getScaledX();
-        int y = component.getScaledY();
-
-        double num = tick * Constants.TICK_DURATION;
-
-        RenderUtils.drawCenteredText(context, MinecraftClient.getInstance().textRenderer, Text.literal(Constants.DECIMAL_FORMAT.format(num)).formatted(Formatting.LIGHT_PURPLE), x, y, component.getWidth());
+        RenderUtils.drawTimer(component, context, tick, Constants.LIGHT_PURPLE);
     }
 }
diff --git a/src/main/java/blade/addon/features/dungeon/f7/DistanceToLedge.java b/src/main/java/blade/addon/features/dungeon/f7/DistanceToLedge.java
index 43329b4..5ee1a17 100644
--- a/src/main/java/blade/addon/features/dungeon/f7/DistanceToLedge.java
+++ b/src/main/java/blade/addon/features/dungeon/f7/DistanceToLedge.java
@@ -1,6 +1,5 @@
 package blade.addon.features.dungeon.f7;
 
-import blade.addon.utils.Constants;
 import blade.addon.utils.Location;
 import blade.addon.utils.config.values.Floor7;
 import blade.addon.utils.dungeon.DungeonClass;
@@ -10,7 +9,6 @@ import config.practical.hud.HUDComponent;
 import net.minecraft.client.MinecraftClient;
 import net.minecraft.client.gui.DrawContext;
 import net.minecraft.client.network.ClientPlayerEntity;
-import net.minecraft.text.Text;
 import net.minecraft.util.math.Box;
 
 public class DistanceToLedge {
@@ -42,9 +40,6 @@ public class DistanceToLedge {
     }
 
     public static void render(HUDComponent component, DrawContext context) {
-        int x = component.getScaledX();
-        int y = component.getScaledY();
-
-        RenderUtils.drawCenteredText(context, MinecraftClient.getInstance().textRenderer, Text.literal(Constants.DECIMAL_FORMAT.format(getDistance())), x, y, component.getWidth());
+        RenderUtils.drawTimer(component, context, getDistance(), 0xffffffff);
     }
 }
diff --git a/src/main/java/blade/addon/features/dungeon/f7/DragSpawnTimer.java b/src/main/java/blade/addon/features/dungeon/f7/DragSpawnTimer.java
index ab20430..f065e03 100644
--- a/src/main/java/blade/addon/features/dungeon/f7/DragSpawnTimer.java
+++ b/src/main/java/blade/addon/features/dungeon/f7/DragSpawnTimer.java
@@ -1,13 +1,11 @@
 package blade.addon.features.dungeon.f7;
 
-import blade.addon.utils.Constants;
 import blade.addon.utils.Scheduler;
 import blade.addon.utils.config.values.Floor7;
 import blade.addon.utils.dungeon.DungeonClass;
 import blade.addon.utils.events.Events;
 import blade.addon.utils.rendering.RenderUtils;
 import config.practical.hud.HUDComponent;
-import net.minecraft.client.MinecraftClient;
 import net.minecraft.client.gui.DrawContext;
 import net.minecraft.particle.ParticleTypes;
 import net.minecraft.sound.SoundEvents;
@@ -59,14 +57,14 @@ public class DragSpawnTimer {
 
     public static void init() {
         Events.ON_PARTICLE.register((x, y, z, effect) -> {
-            if (effect.getType() != ParticleTypes.ENCHANT) return;
+            if (effect.getType() != ParticleTypes.ENCHANT) return false;
             Dragon detectedDragon = getDragon(x, y, z);
 
-            if (detectedDragon == Dragon.NONE) return;
+            if (detectedDragon == Dragon.NONE) return false;
 
             //the dupe check is to stop purple from just breaking it when it spawns
             if (currentDragon == Dragon.NONE) {
-                if ((prevDragon == detectedDragon && dupeTick > 0)) return;
+                if ((prevDragon == detectedDragon && dupeTick > 0)) return false;
                 currentDragon = detectedDragon;
                 prevDragon = currentDragon;
                 dupeTick = DUPE_DELAY;
@@ -80,6 +78,8 @@ public class DragSpawnTimer {
                 dupeTick = DUPE_DELAY * 2;
                 hasDoneSplit = true;
             }
+
+            return false;
         });
 
         Events.ON_SERVER_TICK.register(() -> {
@@ -92,9 +92,13 @@ public class DragSpawnTimer {
             if (tick == 0 && currentDragon != Dragon.NONE) {
                 currentDragon = Dragon.NONE;
             }
+            return false;
         });
 
-        Events.ON_LOCATION_CHANGE.register(newLocation -> reset());
+        Events.ON_LOCATION_CHANGE.register(newLocation -> {
+            reset();
+            return false;
+        });
     }
 
     private static void reset() {
@@ -106,27 +110,19 @@ public class DragSpawnTimer {
 
     //checks are from valley addons
     private static Dragon getDragon(double x, double y, double z) {
-        // check if correct height
         if (y >= 14 && y <= 19) {
-            // check if red/green
             if (x >= 27 && x <= 32) {
-                // check if red
                 if (z == 59) {
                     return Dragon.RED;
-                    // check if green
                 } else if (z == 94) {
                     return Dragon.GREEN;
                 }
-                // check if blue/orange
             } else if (x >= 79 && x <= 85) {
-                // check if blue
                 if (z == 94) {
                     return Dragon.BLUE;
-                    // check if orange
                 } else if (z == 56) {
                     return Dragon.ORANGE;
                 }
-                // check if purple
             } else if (x == 56) {
                 return Dragon.PURPLE;
             }
@@ -156,12 +152,6 @@ public class DragSpawnTimer {
     }
 
     public static void render(HUDComponent component, DrawContext context) {
-        int x = component.getScaledX();
-        int y = component.getScaledY();
-
-        double num = tick * Constants.TICK_DURATION;
-
-        RenderUtils.drawCenteredText(context, MinecraftClient.getInstance().textRenderer, Constants.DECIMAL_FORMAT.format(num), x, y, component.getWidth(), currentDragon.color);
-
+        RenderUtils.drawTimer(component, context, tick, currentDragon.color);
     }
 }
diff --git a/src/main/java/blade/addon/features/dungeon/f7/PillarExplode.java b/src/main/java/blade/addon/features/dungeon/f7/PillarExplode.java
index fad83ec..475081b 100644
--- a/src/main/java/blade/addon/features/dungeon/f7/PillarExplode.java
+++ b/src/main/java/blade/addon/features/dungeon/f7/PillarExplode.java
@@ -21,19 +21,24 @@ public class PillarExplode {
 
     public static void init() {
         Events.ON_GAME_MESSAGE.register(text -> {
-            if (!Floor7.notifyStormCrush && !Floor7.timePillarExplosion) return;
-            if (!Location.inDungeon() || !Phase.inP2()) return;
+            if (!Floor7.notifyStormCrush && !Floor7.timePillarExplosion) return false;
+            if (!Location.inDungeon() || !Phase.inP2()) return false;
 
             String string = text.getString();
-            if (string == null) return;
+            if (string == null) return false;
 
             if (string.equals("[BOSS] Storm: Oof") || string.equals("[BOSS] Storm: Ouch, that hurt!")) {
                 tick = TOTAL_TICKS;
                 Scheduler.scheduleSound(SoundEvents.BLOCK_NOTE_BLOCK_PLING.value(), 1, 1);
             }
+
+            return false;
         });
 
-        Events.ON_SERVER_TICK.register(() -> tick = Math.max(tick - 1, 0));
+        Events.ON_SERVER_TICK.register(() -> {
+            tick = Math.max(tick - 1, 0);
+            return false;
+        });
     }
 
     public static boolean displayTimer() {
@@ -41,15 +46,8 @@ public class PillarExplode {
     }
 
     public static void renderTimer(HUDComponent component, DrawContext context) {
-        int x = component.getScaledX();
-        int y = component.getScaledY();
-
-        Formatting formatting = (tick < 6 ? Formatting.GREEN : Formatting.RED);
-
-        double num = tick * Constants.TICK_DURATION;
-
-        RenderUtils.drawCenteredText(context, MinecraftClient.getInstance().textRenderer, Text.literal(Constants.DECIMAL_FORMAT.format(num).formatted(formatting)), x, y, component.getWidth());
-
+        int color = tick < 6 ? Constants.GREEN_COLOR : Constants.RED_COLOR;
+        RenderUtils.drawTimer(component, context, tick, color);
     }
 
     public static boolean display() {
diff --git a/src/main/java/blade/addon/features/dungeon/f7/PredevTimer.java b/src/main/java/blade/addon/features/dungeon/f7/PredevTimer.java
index bf97020..bc7d91e 100644
--- a/src/main/java/blade/addon/features/dungeon/f7/PredevTimer.java
+++ b/src/main/java/blade/addon/features/dungeon/f7/PredevTimer.java
@@ -26,6 +26,7 @@ public class PredevTimer {
             } else if (Phase.inP3()) {
                 shouldTrack = false;
             }
+            return false;
         });
 
         Events.ON_LOCATION_CHANGE.register(newLocation -> shouldTrack = false);
@@ -45,6 +46,7 @@ public class PredevTimer {
                 at3rdDev = false;
                 shouldTrack = false;
             }
+            return false;
         });
 
     }
diff --git a/src/main/java/blade/addon/features/dungeon/f7/RelicTimer.java b/src/main/java/blade/addon/features/dungeon/f7/RelicTimer.java
index 74939b1..c58cec4 100644
--- a/src/main/java/blade/addon/features/dungeon/f7/RelicTimer.java
+++ b/src/main/java/blade/addon/features/dungeon/f7/RelicTimer.java
@@ -69,13 +69,15 @@ public class RelicTimer {
     public static void init() {
         Events.ON_SERVER_TICK.register(() -> {
             if (!forceGUI) {
-                if (!Location.inDungeon() || !Phase.inP5()) return;
+                if (!Location.inDungeon() || !Phase.inP5()) return false;
             }
             tick = Math.max(tick - 1, -1);
 
             if (tick == -1) {
                 forceGUI = false;
             }
+
+            return false;
         });
 
         Events.ON_LOCATION_CHANGE.register(newLocation -> {
@@ -86,12 +88,15 @@ public class RelicTimer {
                     relic.placedTime = 0;
                 }
             }
+
+            return false;
         });
 
         Events.ON_PHASE_CHANGE.register(() -> {
             if (Phase.inP5()) {
                 phaseStartTime = System.currentTimeMillis();
             }
+            return false;
         });
 
         ClientReceiveMessageEvents.GAME.register((message, overlay) -> {
@@ -112,15 +117,15 @@ public class RelicTimer {
         });
 
         Events.ON_ENTITY_SPAWNED.register((entity, world) -> {
-            if (!Location.inDungeon() || !Phase.inP5() || !Floor7.showAllRelicTimes) return;
+            if (!Location.inDungeon() || !Phase.inP5() || !Floor7.showAllRelicTimes) return false;
 
 
             if (entity instanceof ArmorStandEntity armorStand) {
                 ItemStack helmet = armorStand.getEquippedStack(EquipmentSlot.HEAD);
                 Text text = helmet.getName();
-                if (text == null) return;
+                if (text == null) return false;
                 String name = text.getString();
-                if (!name.contains("Relic")) return;
+                if (!name.contains("Relic")) return false;
 
                 double ax = armorStand.getX();
                 double az = armorStand.getZ();
@@ -139,7 +144,7 @@ public class RelicTimer {
                     }
                 }
             }
-
+            return false;
         });
 
         Events.ON_BLOCK_INTERACTION.register((result, itemStack) -> {
@@ -216,15 +221,8 @@ public class RelicTimer {
     }
 
     public static void render(HUDComponent component, DrawContext context) {
-        int x = component.getScaledX();
-        int y = component.getScaledY();
-
         int color = tick > 7 ? GREEN_COLOR : RED_COLOR;
-
-        double num = tick * Constants.TICK_DURATION;
-
-        RenderUtils.drawCenteredText(context, MinecraftClient.getInstance().textRenderer, Text.literal(Constants.DECIMAL_FORMAT.format(num)), x, y, component.getWidth(), color);
-
+        RenderUtils.drawTimer(component, context, tick, color);
     }
 
     public static boolean displayProgressBar() {
@@ -237,7 +235,6 @@ public class RelicTimer {
         int y = component.getScaledY();
 
         //from valleyAddons
-
         StringBuilder message = new StringBuilder("§8[");
 
         if (Floor7.useValleyBar) {
diff --git a/src/main/java/blade/addon/features/dungeon/f7/StormTickTimer.java b/src/main/java/blade/addon/features/dungeon/f7/StormTickTimer.java
index 86c35d8..ca7b86b 100644
--- a/src/main/java/blade/addon/features/dungeon/f7/StormTickTimer.java
+++ b/src/main/java/blade/addon/features/dungeon/f7/StormTickTimer.java
@@ -4,17 +4,14 @@ import blade.addon.features.dungeon.f7.invincibility.InvincibilityTimer;
 import blade.addon.utils.Constants;
 import blade.addon.utils.Location;
 import blade.addon.utils.Misc;
-import blade.addon.utils.Scheduler;
 import blade.addon.utils.config.values.Floor7;
 import blade.addon.utils.dungeon.DungeonClass;
 import blade.addon.utils.dungeon.Phase;
 import blade.addon.utils.events.Events;
 import blade.addon.utils.rendering.RenderUtils;
 import config.practical.hud.HUDComponent;
-import net.minecraft.client.MinecraftClient;
 import net.minecraft.client.gui.DrawContext;
 import net.minecraft.text.Text;
-import net.minecraft.util.Formatting;
 
 import java.util.regex.Matcher;
 import java.util.regex.Pattern;
@@ -40,6 +37,7 @@ public class StormTickTimer {
             if (tick == WARN_TICK && Floor7.notifyUsedSpiritMask && DungeonClass.isClass(DungeonClass.MAGE) && InvincibilityTimer.spiritMaskUsed()) {
                 Misc.setTitle(Text.literal("Leap to arch"));
             }
+            return false;
         });
 
         Events.ON_LOCATION_CHANGE.register(newLocation -> {
@@ -48,26 +46,21 @@ public class StormTickTimer {
                 deathTime = 0;
                 deathStartDisplayTime = 0;
             }
+            return false;
         });
 
         Events.ON_GAME_MESSAGE.register(text -> {
-            if (!Location.inDungeon() || !Phase.inP2() || !Floor7.enableStormDeathTime) return;
+            if (!Location.inDungeon() || !Phase.inP2() || !Floor7.enableStormDeathTime) return false;
 
             Matcher matcher = PATTERN.matcher(text.getString());
 
             if (matcher.find()) {
                 deathTime = (tick * Constants.TICK_DURATION);
                 deathStartDisplayTime = System.currentTimeMillis();
-
-                //prob dosent need to schedule it but threads are scary
-                Scheduler.scheduleTask(() -> Misc.addChatMessage(
-                        Text.literal("Storm died at: ").formatted(Formatting.GREEN)
-                                .append(Text.literal(Constants.DECIMAL_FORMAT.format(deathTime) + "s").formatted(Formatting.YELLOW))
-                                .append(Text.literal(".").formatted(Formatting.GREEN))
-                ), 1);
-
+                Misc.addChatMessage(Text.literal("§aStorm died at: §e" + Constants.DECIMAL_FORMAT.format(deathTime) + "s§a."));
             }
 
+            return false;
         });
     }
 
@@ -81,18 +74,11 @@ public class StormTickTimer {
     }
 
     public static void render(HUDComponent component, DrawContext context) {
-        int x = component.getScaledX();
-        int y = component.getScaledY();
-
         double num = tick * Constants.TICK_DURATION;
-
         if (Floor7.tickDownStormTickTimer) {
             num = CRUSH_TICK * Constants.TICK_DURATION - num;
-
         }
-
-        RenderUtils.drawCenteredText(context, MinecraftClient.getInstance().textRenderer, Constants.DECIMAL_FORMAT.format(num), x, y, component.getWidth(), Floor7.stormTickTimerColor);
-
+        RenderUtils.drawTimer(component, context, num, Floor7.stormTickTimerColor);
     }
 
     public static boolean displayDeathTime() {
@@ -100,10 +86,6 @@ public class StormTickTimer {
     }
 
     public static void renderDeathTime(HUDComponent component, DrawContext context) {
-        int x = component.getScaledX();
-        int y = component.getScaledY();
-
-        RenderUtils.drawCenteredText(context, MinecraftClient.getInstance().textRenderer, Text.literal(Constants.DECIMAL_FORMAT.format(deathTime)).formatted(Formatting.DARK_PURPLE), x, y, component.getWidth());
-
+        RenderUtils.drawTimer(component, context, deathTime, Constants.DARK_PURPLE);
     }
 }
diff --git a/src/main/java/blade/addon/features/dungeon/f7/invincibility/InvincibilityTimer.java b/src/main/java/blade/addon/features/dungeon/f7/invincibility/InvincibilityTimer.java
index b506325..6f875f0 100644
--- a/src/main/java/blade/addon/features/dungeon/f7/invincibility/InvincibilityTimer.java
+++ b/src/main/java/blade/addon/features/dungeon/f7/invincibility/InvincibilityTimer.java
@@ -14,7 +14,6 @@ import net.minecraft.client.gl.RenderPipelines;
 import net.minecraft.client.gui.DrawContext;
 import net.minecraft.item.ItemStack;
 import net.minecraft.text.Text;
-import net.minecraft.util.Formatting;
 import net.minecraft.util.Identifier;
 
 import java.util.regex.Matcher;
@@ -29,10 +28,6 @@ public class InvincibilityTimer {
     private static final Identifier SPIRIT_SPRITE = Identifier.of(Constants.NAMESPACE, "spirit-mask");
     private static final Identifier PHOENIX_SPRITE = Identifier.of(Constants.NAMESPACE, "phoenix");
 
-    private static final int GREEN_COLOR = 0x8800FF00;
-    private static final int YELLOW_COLOR = 0x88FFFF00;
-    private static final int RED_COLOR = 0x88FF0000;
-
     private static final int SPRITE_SIZE = TEXT_HEIGHT;
 
     private static final Pattern BONZO_PATTERN = Pattern.compile("^Your (?:\\S+ )?Bonzo's Mask saved your life!$");
@@ -78,6 +73,7 @@ public class InvincibilityTimer {
                 spiritMaskTicks = 0;
                 phoenixTicks = 0;
             }
+            return false;
         });
 
         Events.ON_PET.register(name -> phoenixOn = name.equals("Phoenix"));
@@ -90,23 +86,23 @@ public class InvincibilityTimer {
         });
     }
 
-    private static void tick() {
+    private static boolean tick() {
         bonzoMaskTicks = Math.max(0, bonzoMaskTicks - 1);
         spiritMaskTicks = Math.max(0, spiritMaskTicks - 1);
         phoenixTicks = Math.max(0, phoenixTicks - 1);
+        return false;
     }
 
-    private static void detectHelmet(int slot, ItemStack item) {
-        if (slot != HEAD_SLOT || !Location.inDungeon()) return;
-        Text text = item.getCustomName();
-        if (text == null) return;
-        String string = text.getString();
+    private static boolean detectHelmet(int slot, ItemStack item) {
+        if (slot != HEAD_SLOT || !Location.inDungeon()) return false;
+        String string = item.getName().getString();
 
         Matcher matcher = BONZO_ON_HEAD_PATTERN.matcher(string);
         bonzoMaskOn = matcher.find();
 
         matcher = SPIRIT_ON_HEAD_PATTERN.matcher(string);
         spiritMaskOn = matcher.find();
+        return false;
     }
 
     private static void parseMessage(Text message) {
@@ -134,37 +130,15 @@ public class InvincibilityTimer {
     }
 
     private static Text formatTimer(int ticks) {
-        return Text.literal("(").formatted(Formatting.DARK_GRAY)
-                .append(Text.literal(Constants.DECIMAL_FORMAT.format(ticks * Constants.TICK_DURATION)).formatted(Formatting.GRAY))
-                .append(Text.literal(")").formatted(Formatting.DARK_GRAY));
-    }
-
-
-    private static Text getBonzoText() {
-        if (bonzoMaskTicks > 0) {
-            return Text.literal("Bonzo's Mask ").formatted(Formatting.RED).append(formatTimer(bonzoMaskTicks));
-        } else {
-            Formatting format = bonzoMaskOn ? Formatting.YELLOW : Formatting.GREEN;
-            return Text.literal("Bonzo's Mask ").formatted(format);
-        }
-    }
-
-    private static Text getSpiritText() {
-        if (spiritMaskTicks > 0) {
-            return Text.literal("Spirit Mask ").formatted(Formatting.RED).append(formatTimer(spiritMaskTicks));
-
-        } else {
-            Formatting format = spiritMaskOn ? Formatting.YELLOW : Formatting.GREEN;
-            return Text.literal("Spirit Mask ").formatted(format);
-        }
+        return Text.literal("§8(§7" + Constants.DECIMAL_FORMAT.format(ticks * Constants.TICK_DURATION) + "§8)");
     }
 
-    private static Text getPhoenixText() {
-        if (phoenixTicks > 0) {
-            return Text.literal("Phoenix ").formatted(Formatting.RED).append(formatTimer(phoenixTicks));
+    private static Text getText(int ticks, boolean isOn, String string) {
+        if (ticks > 0) {
+            return Text.literal("§c" + string).append(formatTimer(ticks));
         } else {
-            Formatting format = phoenixOn ? Formatting.YELLOW : Formatting.GREEN;
-            return Text.literal("Phoenix ").formatted(format);
+            String color  = isOn ? "§e" : "§a";
+            return Text.literal(color + string);
         }
     }
 
@@ -177,22 +151,9 @@ public class InvincibilityTimer {
     }
 
     private static void drawSprite(DrawContext context, Identifier identifier, int x, int y, boolean isOn, int ticks, Text timerText) {
-
-        int color;
-
-        if (ticks > 0) {
-            color = RED_COLOR;
-        } else if (isOn) {
-            color = YELLOW_COLOR;
-        } else {
-            color = GREEN_COLOR;
-        }
-
-
+        int color = (ticks > 0? Constants.RED_COLOR: isOn? Constants.YELLOW_COLOR: Constants.GREEN_COLOR);
         context.fill(x, y, x + SPRITE_SIZE, y + SPRITE_SIZE, color);
-
         context.drawGuiTexture(RenderPipelines.GUI_TEXTURED, identifier, x, y, SPRITE_SIZE, SPRITE_SIZE, 0xffffffff);
-
         if (ticks > 0) {
             context.drawText(MinecraftClient.getInstance().textRenderer, timerText, x + TEXT_HEIGHT * 2, y, 0xffffffff, true);
         }
@@ -235,11 +196,10 @@ public class InvincibilityTimer {
             drawSprite(context, BONZO_SPRITE, x, y, bonzoMaskOn, bonzoMaskTicks, formatTimer(bonzoMaskTicks));
             drawSprite(context, SPIRIT_SPRITE, x, y + SPRITE_SIZE + 1, spiritMaskOn, spiritMaskTicks, formatTimer(spiritMaskTicks));
             drawSprite(context, PHOENIX_SPRITE, x, y + (SPRITE_SIZE + 1) * 2, phoenixOn, phoenixTicks, formatTimer(phoenixTicks));
-
         } else {
-            context.drawText(MinecraftClient.getInstance().textRend
…… diff 过长已截断 ……
- hopefully fixed msgs some times crashing the gameGabriel Ellnebrand · 2025-12-31 · 79ee4f7
查看变更
src/main/java/blade/addon/features/dungeon/AutoRequeue.java   | 10 +++-------
 src/main/java/blade/addon/features/dungeon/ExplosiveShot.java |  7 +------
 src/main/java/blade/addon/utils/Misc.java                     |  9 ++++++++-
 src/main/java/blade/addon/utils/Scheduler.java                |  1 -
 src/main/java/blade/addon/utils/dungeon/Phase.java            |  5 ++---
 5 files changed, 14 insertions(+), 18 deletions(-)
diff --git a/src/main/java/blade/addon/features/dungeon/AutoRequeue.java b/src/main/java/blade/addon/features/dungeon/AutoRequeue.java
index 6bfb950..c138439 100644
--- a/src/main/java/blade/addon/features/dungeon/AutoRequeue.java
+++ b/src/main/java/blade/addon/features/dungeon/AutoRequeue.java
@@ -5,10 +5,7 @@ import blade.addon.utils.Misc;
 import blade.addon.utils.config.values.Dungeons;
 import blade.addon.utils.events.Events;
 import net.fabricmc.fabric.api.client.message.v1.ClientReceiveMessageEvents;
-import net.minecraft.client.MinecraftClient;
-import net.minecraft.client.gui.hud.ChatHud;
 import net.minecraft.text.Text;
-import net.minecraft.util.Formatting;
 
 import java.util.HashMap;
 import java.util.regex.Matcher;
@@ -31,13 +28,13 @@ public class AutoRequeue {
                 Matcher matcher = UNDT_PATTERN.matcher(message);
                 if (matcher.find()) {
                     playerHashMap.remove(username);
-                    Misc.addChatMessage(Text.literal("Downtime removed").formatted(Formatting.GREEN));
+                    Misc.addChatMessage(Text.literal("§aDowntime removed"));
                 }
             } else {
                 Matcher matcher = DT_PATTERN.matcher(message);
                 if (matcher.find()) {
                     playerHashMap.put(username, message);
-                    Misc.addChatMessage(Text.literal("Downtime added").formatted(Formatting.GREEN));
+                    Misc.addChatMessage(Text.literal("§aDowntime added"));
                 }
             }
         });
@@ -57,8 +54,7 @@ public class AutoRequeue {
                 Misc.executeCommand("instancerequeue");
             } else {
                 Misc.addChatMessage(Text.literal("Downtime reasons:"));
-                ChatHud chathud = MinecraftClient.getInstance().inGameHud.getChatHud();
-                playerHashMap.forEach((name, string) -> chathud.addMessage(Text.literal(name + "> " + string)));
+                playerHashMap.forEach((name, string) -> Misc.addChatMessage((Text.literal(name + "> " + string))));
 
 
             }
diff --git a/src/main/java/blade/addon/features/dungeon/ExplosiveShot.java b/src/main/java/blade/addon/features/dungeon/ExplosiveShot.java
index 3fdcbd4..dd8fb0e 100644
--- a/src/main/java/blade/addon/features/dungeon/ExplosiveShot.java
+++ b/src/main/java/blade/addon/features/dungeon/ExplosiveShot.java
@@ -6,7 +6,6 @@ import blade.addon.utils.config.values.Dungeons;
 import blade.addon.utils.dungeon.Phase;
 import net.fabricmc.fabric.api.client.message.v1.ClientReceiveMessageEvents;
 import net.minecraft.text.Text;
-import net.minecraft.util.Formatting;
 
 import java.text.DecimalFormat;
 import java.util.regex.Matcher;
@@ -38,11 +37,7 @@ public class ExplosiveShot {
 
                     double damagePerEntity = amountNum / countNum;
 
-                    Misc.addChatMessage(
-                            Text.literal("Explosive shot did ").formatted(Formatting.GREEN)
-                                    .append(Text.literal(FORMAT.format(damagePerEntity)).formatted(Formatting.YELLOW))
-                                    .append(Text.literal(" damage per enemy.").formatted(Formatting.GREEN))
-                    );
+                    Misc.addChatMessage(Text.literal("§aExplosive shot did §e" + FORMAT.format(damagePerEntity) +"§a damage per enemy."));
 
                 } catch (NumberFormatException ignored) {
                 }
diff --git a/src/main/java/blade/addon/utils/Misc.java b/src/main/java/blade/addon/utils/Misc.java
index d55849c..6ff4c97 100644
--- a/src/main/java/blade/addon/utils/Misc.java
+++ b/src/main/java/blade/addon/utils/Misc.java
@@ -3,6 +3,7 @@ package blade.addon.utils;
 import blade.addon.utils.config.values.ExtraOptions;
 import blade.addon.utils.interfaces.GameHud;
 import net.minecraft.client.MinecraftClient;
+import net.minecraft.client.gui.hud.ChatHud;
 import net.minecraft.client.gui.hud.InGameHud;
 import net.minecraft.client.network.ClientPlayNetworkHandler;
 import net.minecraft.client.network.ClientPlayerEntity;
@@ -46,7 +47,13 @@ public class Misc {
     public static void addChatMessage(Text text) {
         try {
             InGameHud gameHud = INSTANCE.inGameHud;
-            gameHud.getChatHud().addMessage(Text.literal(ExtraOptions.textPrefix).append(text));
+            ChatHud hud = gameHud.getChatHud();
+            if (INSTANCE.isOnThread()) {
+                hud.addMessage(Text.literal(ExtraOptions.textPrefix).append(text));
+            } else {
+                INSTANCE.executeSync(() ->  hud.addMessage(Text.literal(ExtraOptions.textPrefix).append(text)));
+            }
+
         } catch (IndexOutOfBoundsException ignored) {
             Debug.LOGGER.error("Chat message failed to get added");
         }
diff --git a/src/main/java/blade/addon/utils/Scheduler.java b/src/main/java/blade/addon/utils/Scheduler.java
index 8789083..14ab789 100644
--- a/src/main/java/blade/addon/utils/Scheduler.java
+++ b/src/main/java/blade/addon/utils/Scheduler.java
@@ -7,7 +7,6 @@ import net.minecraft.client.MinecraftClient;
 import net.minecraft.client.gui.screen.Screen;
 import net.minecraft.client.network.ClientPlayerEntity;
 import net.minecraft.sound.SoundEvent;
-import net.minecraft.sound.SoundEvents;
 
 import java.util.concurrent.CopyOnWriteArrayList;
 
diff --git a/src/main/java/blade/addon/utils/dungeon/Phase.java b/src/main/java/blade/addon/utils/dungeon/Phase.java
index d73c14e..264dda3 100644
--- a/src/main/java/blade/addon/utils/dungeon/Phase.java
+++ b/src/main/java/blade/addon/utils/dungeon/Phase.java
@@ -12,7 +12,6 @@ import config.practical.manager.ConfigValue;
 import net.minecraft.client.MinecraftClient;
 import net.minecraft.client.font.TextRenderer;
 import net.minecraft.text.Text;
-import net.minecraft.util.Formatting;
 
 import java.util.ArrayList;
 import java.util.HashMap;
@@ -147,14 +146,14 @@ public class Phase {
         runOver = true;
         currentPhase = currentSplits.size();
 
-        Misc.addChatMessage(Text.literal("Splits: ").formatted(Formatting.GREEN));
+        Misc.addChatMessage(Text.literal("§aSplits: "));
         for (Split split : currentSplits) {
             split.end();
             Misc.addChatMessage(split.createNameText().append(split.createTimeText()));
         }
         if (!currentSplits.isEmpty()) {
             double time = currentSplits.getLast().getTimeDiffrence();
-            Text timeLost = Text.literal("Approximately ").formatted(Formatting.GREEN).append(net.minecraft.text.Text.literal(Constants.DECIMAL_FORMAT.format(time) + "s ").formatted(Formatting.YELLOW)).append(net.minecraft.text.Text.literal("lost to lag.").formatted(Formatting.GREEN));
+            Text timeLost = Text.literal("§aApproximately §e" + Constants.DECIMAL_FORMAT.format(time) + "s §alost to lag.");
             Misc.addChatMessage(timeLost);
         }
 
- ported to 1.21.10Gabriel Ellnebrand · 2025-12-31 · 5ab38de
查看变更
gradle.properties                                                                         | 16 +++++------
 gradle/wrapper/gradle-wrapper.properties                                                  |  2 +-
 src/main/java/blade/addon/Bladeaddons.java                                                | 10 ++++---
 src/main/java/blade/addon/features/dungeon/HidePlayers.java                               |  7 +++--
 src/main/java/blade/addon/features/dungeon/ItemHighlight.java                             | 29 ++++++++++----------
 src/main/java/blade/addon/features/dungeon/LeapOrder.java                                 |  9 ++++---
 src/main/java/blade/addon/features/dungeon/f7/BossWaypoints.java                          | 43 ++++++++++++++---------------
 src/main/java/blade/addon/features/dungeon/f7/DistanceToLedge.java                        |  2 +-
 src/main/java/blade/addon/features/dungeon/f7/RelicTimer.java                             | 23 +++++++---------
 src/main/java/blade/addon/features/dungeon/f7/location/PositionMessage.java               |  2 +-
 src/main/java/blade/addon/features/dungeon/f7/location/PositionMessages.java              | 28 +++++++++----------
 src/main/java/blade/addon/features/dungeon/f7/terms/LeapNotification.java                 |  2 +-
 src/main/java/blade/addon/features/highlight/MimicHighlight.java                          | 92 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 src/main/java/blade/addon/features/highlight/MobHighlight.java                            | 60 ++++++++++++++++++++---------------------
 src/main/java/blade/addon/features/other/InventoryButton.java                             |  6 ++---
 src/main/java/blade/addon/features/other/PracticeSS.java                                  | 30 +++++++++------------
 src/main/java/blade/addon/mixin/BlockEntityRenderMixin.java                               | 43 -----------------------------
 src/main/java/blade/addon/mixin/ChunkMixin.java                                           | 20 ++++++++++++++
 src/main/java/blade/addon/mixin/{ServerTickMixin.java => ClientConnectionMixin.java}      |  2 +-
 src/main/java/blade/addon/mixin/{NetworkMixin.java => ClientPlayNetworkHandlerMixin.java} | 13 ++++++---
 src/main/java/blade/addon/mixin/{RenderMixin.java => EntityRenderManagerMixin.java}       |  6 ++---
 src/main/java/blade/addon/mixin/EntityRendererMixin.java                                  | 22 ++++++++++++---
 src/main/java/blade/addon/mixin/FireMixin.java                                            | 26 ------------------
 src/main/java/blade/addon/mixin/InventoryScreenMixin.java                                 | 13 +++------
 src/main/java/blade/addon/mixin/{InventoryMixin.java => PlayerInventoryMixin.java}        |  4 +--
 src/main/java/blade/addon/mixin/PositionMixin.java                                        | 21 ---------------
 src/main/java/blade/addon/mixin/{ArrowMixin.java => StuckArrowsFeatureRendererMixin.java} |  2 +-
 src/main/java/blade/addon/utils/Constants.java                                            |  2 +-
 src/main/java/blade/addon/utils/Keybinds.java                                             | 11 ++++----
 src/main/java/blade/addon/utils/config/Config.java                                        |  2 +-
 src/main/java/blade/addon/utils/dungeon/DungeonClass.java                                 | 13 ++++++++-
 src/main/java/blade/addon/utils/events/Events.java                                        |  3 +++
 src/main/java/blade/addon/utils/events/interfaces/BlockEntityEvent.java                   |  9 +++++++
 src/main/resources/blade-addons.mixins.json                                               | 16 +++++------
 34 files changed, 323 insertions(+), 266 deletions(-)
diff --git a/gradle.properties b/gradle.properties
index 5926cac..a392cc6 100644
--- a/gradle.properties
+++ b/gradle.properties
@@ -7,19 +7,19 @@ org.gradle.configuration-cache=false
 
 # Fabric Properties
 # check these on https://fabricmc.net/develop
-minecraft_version=1.21.8
-yarn_mappings=1.21.8+build.1
-loader_version=0.17.3
-loom_version=1.13-SNAPSHOT
+minecraft_version=1.21.10
+yarn_mappings=1.21.10+build.3
+loader_version=0.18.4
+loom_version=1.14-SNAPSHOT
 
 # Mod Properties
-mod_version=2.2.6
+mod_version=2.3.0
 maven_group=blade.addon
 archives_base_name=blade-addons
 
 # Dependencies
-fabric_version=0.136.0+1.21.8
+fabric_version=0.138.4+1.21.10
 # I love dependencies sometimes breaking
-practical_config=1.3.7
-modmenu_version = 15.0.0
+practical_config=74a456703958c0a6381956ccf9be4a5e8dd7d84f
+modmenu_version = 16.0.0-rc.2
 hypixel_api_version = 1.0.1
\ No newline at end of file
diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties
index 2e11132..23449a2 100644
--- a/gradle/wrapper/gradle-wrapper.properties
+++ b/gradle/wrapper/gradle-wrapper.properties
@@ -1,6 +1,6 @@
 distributionBase=GRADLE_USER_HOME
 distributionPath=wrapper/dists
-distributionUrl=https\://services.gradle.org/distributions/gradle-9.1.0-bin.zip
+distributionUrl=https\://services.gradle.org/distributions/gradle-9.2.1-bin.zip
 networkTimeout=10000
 validateDistributionUrl=true
 zipStoreBase=GRADLE_USER_HOME
diff --git a/src/main/java/blade/addon/Bladeaddons.java b/src/main/java/blade/addon/Bladeaddons.java
index 2a78ffc..27e3dc8 100644
--- a/src/main/java/blade/addon/Bladeaddons.java
+++ b/src/main/java/blade/addon/Bladeaddons.java
@@ -27,6 +27,7 @@ import blade.addon.features.dungeon.f7.PredevTimer;
 import blade.addon.features.dungeon.f7.RelicTimer;
 import blade.addon.features.dungeon.f7.StormTickTimer;
 import blade.addon.features.dungeon.f7.terms.TermStartTimer;
+import blade.addon.features.highlight.MimicHighlight;
 import blade.addon.features.highlight.MobHighlight;
 import blade.addon.features.item.DropAnimation;
 import blade.addon.features.other.CompactHoppity;
@@ -51,17 +52,17 @@ import blade.addon.utils.events.CustomEvents;
 import net.fabricmc.api.ModInitializer;
 
 public class Bladeaddons implements ModInitializer {
-	@Override
-	public void onInitialize() {
+    @Override
+    public void onInitialize() {
         FolderUtility.init();
         Components.init();
         Buttons.init();
         Config.manager.load();
-		Keybinds.init();
+        Keybinds.init();
         CustomEvents.init();
         Commands.init();
         Debug.init();
-		Location.init();
+        Location.init();
         Phase.init();
         Section.init();
         DungeonClass.init();
@@ -101,5 +102,6 @@ public class Bladeaddons implements ModInitializer {
         PracticeSS.init();
         CompactHoppity.init();
         DropAnimation.init();
+        MimicHighlight.init();
     }
 }
\ No newline at end of file
diff --git a/src/main/java/blade/addon/features/dungeon/HidePlayers.java b/src/main/java/blade/addon/features/dungeon/HidePlayers.java
index 9e4efb6..518b023 100644
--- a/src/main/java/blade/addon/features/dungeon/HidePlayers.java
+++ b/src/main/java/blade/addon/features/dungeon/HidePlayers.java
@@ -45,7 +45,7 @@ public class HidePlayers {
         if (!Dungeons.hideAtSS || !Phase.inBoss()) return false;
         if (Dungeons.hideBeforeTermsOnly && Phase.inP3()) return false;
 
-        return SS_POSITION.distanceTo(player.getPos()) <= DISTANCE;
+        return SS_POSITION.distanceTo(player.getEntityPos()) <= DISTANCE;
     }
 
     public static boolean shouldHidePlayers(PlayerEntity player) {
@@ -65,7 +65,10 @@ public class HidePlayers {
         if (Dungeons.hidePlayersInRange) {
             ClientPlayerEntity clientPlayer = MinecraftClient.getInstance().player;
             if (clientPlayer == null) return false;
-            return player.getPos().distanceTo(clientPlayer.getPos()) <= Dungeons.hidePlayerRange;
+
+            double distance = player.getEntityPos().distanceTo(clientPlayer.getEntityPos());
+            if (!Double.isFinite(distance) || !Double.isFinite(Dungeons.hidePlayerRange)) return false;
+            return distance <= Dungeons.hidePlayerRange;
         }
 
         return false;
diff --git a/src/main/java/blade/addon/features/dungeon/ItemHighlight.java b/src/main/java/blade/addon/features/dungeon/ItemHighlight.java
index 6b115bd..6b2990a 100644
--- a/src/main/java/blade/addon/features/dungeon/ItemHighlight.java
+++ b/src/main/java/blade/addon/features/dungeon/ItemHighlight.java
@@ -8,10 +8,9 @@ import blade.addon.utils.rendering.RenderLayers;
 import blade.addon.utils.rendering.RenderUtils;
 import net.fabricmc.fabric.api.client.event.lifecycle.v1.ClientEntityEvents;
 import net.fabricmc.fabric.api.client.event.lifecycle.v1.ClientTickEvents;
-import net.fabricmc.fabric.api.client.rendering.v1.WorldRenderEvents;
+import net.fabricmc.fabric.api.client.rendering.v1.world.WorldRenderEvents;
 import net.minecraft.client.MinecraftClient;
 import net.minecraft.client.network.ClientPlayerEntity;
-import net.minecraft.client.render.Camera;
 import net.minecraft.client.render.VertexConsumer;
 import net.minecraft.client.render.VertexConsumerProvider;
 import net.minecraft.client.render.VertexRendering;
@@ -58,21 +57,21 @@ public class ItemHighlight {
             }
         });
 
-        WorldRenderEvents.BEFORE_DEBUG_RENDER.register(worldRenderContext -> {
+        WorldRenderEvents.BEFORE_DEBUG_RENDER.register(context -> {
             if (!Dungeons.highlightItems) return;
-            Camera camera = worldRenderContext.camera();
-            Vec3d cameraPos = camera.getPos();
-            MatrixStack matrixStack = worldRenderContext.matrixStack();
-            if (matrixStack == null) return;
-            matrixStack.push();
-            matrixStack.translate(-cameraPos.x, -cameraPos.y, -cameraPos.z);
-
-            VertexConsumerProvider consumers = worldRenderContext.consumers();
+            Vec3d camera = context.worldState().cameraRenderState.pos;
+            MatrixStack matrices = context.matrices();
+            if (matrices == null) return;
+            matrices.push();
+            matrices.translate(-camera.x, -camera.y, -camera.z);
+
+            VertexConsumerProvider consumers = context.consumers();
             if (consumers == null) return;
             VertexConsumer buffer = consumers.getBuffer(RenderLayers.THROUGH_WALL_FILLED_LAYER);
 
+            double tickProgress = MinecraftClient.getInstance().getRenderTickCounter().getTickProgress(false);
+
             trackedItems.forEach((itemEntity, integer) -> {
-                double tickProgress = worldRenderContext.tickCounter().getTickProgress(false);
                 double x =  MathHelper.lerp(tickProgress, itemEntity.lastRenderX, itemEntity.getX());
                 double y =  MathHelper.lerp(tickProgress, itemEntity.lastRenderY, itemEntity.getY());
                 double z =  MathHelper.lerp(tickProgress, itemEntity.lastRenderZ, itemEntity.getZ());
@@ -80,10 +79,10 @@ public class ItemHighlight {
                 EntityDimensions dimension = itemEntity.getDimensions(itemEntity.getPose());
                 Box box = dimension.getBoxAt(x, y, z).expand(0.1).offset(0, 0.05, 0);
                 float[] color = RenderUtils.toFloats(getColor(itemEntity));
-                VertexRendering.drawFilledBox(matrixStack, buffer, box.minX, box.minY, box.minZ, box.maxX, box.maxY, box.maxZ, color[0], color[1], color[2], color[3]);
+                VertexRendering.drawFilledBox(matrices, buffer, box.minX, box.minY, box.minZ, box.maxX, box.maxY, box.maxZ, color[0], color[1], color[2], color[3]);
             });
 
-            matrixStack.pop();
+            matrices.pop();
         });
     }
 
@@ -100,7 +99,7 @@ public class ItemHighlight {
     public static int getColor(ItemEntity item) {
         ClientPlayerEntity player = MinecraftClient.getInstance().player;
         if (player == null) return 0;
-        double distance = item.getPos().distanceTo(player.getPos());
+        double distance = item.getEntityPos().distanceTo(player.getEntityPos());
         if (distance > 20) {
             return 0;
         } else if (distance > 3.5) {
diff --git a/src/main/java/blade/addon/features/dungeon/LeapOrder.java b/src/main/java/blade/addon/features/dungeon/LeapOrder.java
index 526faa3..aebaf53 100644
--- a/src/main/java/blade/addon/features/dungeon/LeapOrder.java
+++ b/src/main/java/blade/addon/features/dungeon/LeapOrder.java
@@ -1,9 +1,11 @@
 package blade.addon.features.dungeon;
 
+import blade.addon.utils.Debug;
 import blade.addon.utils.Misc;
 import blade.addon.utils.dungeon.DungeonClass;
 import net.minecraft.client.MinecraftClient;
 import net.minecraft.client.network.ClientPlayerEntity;
+import net.minecraft.client.world.ClientWorld;
 import net.minecraft.scoreboard.Scoreboard;
 import net.minecraft.scoreboard.Team;
 import net.minecraft.text.Text;
@@ -30,12 +32,13 @@ public class LeapOrder {
         DungeonClass playersClass = null;
 
         ClientPlayerEntity player = MinecraftClient.getInstance().player;
-        if (player == null) {
-            Misc.addChatMessage(Text.literal("Player is somehow null"));
+        ClientWorld world = MinecraftClient.getInstance().world;
+        if (player == null || world == null) {
+            Debug.LOGGER.warn("Player or World is null");
             return;
         }
 
-        Scoreboard scoreboard = player.getScoreboard();
+        Scoreboard scoreboard = world.getScoreboard();
         for (Team team : scoreboard.getTeams()) {
             String teamStr = team.getPrefix().getString() + team.getSuffix().getString();
             Matcher matcher = PATTERN.matcher(teamStr);
diff --git a/src/main/java/blade/addon/features/dungeon/f7/BossWaypoints.java b/src/main/java/blade/addon/features/dungeon/f7/BossWaypoints.java
index 091927c..2a1ee5d 100644
--- a/src/main/java/blade/addon/features/dungeon/f7/BossWaypoints.java
+++ b/src/main/java/blade/addon/features/dungeon/f7/BossWaypoints.java
@@ -12,10 +12,9 @@ import com.google.gson.Gson;
 import com.google.gson.JsonArray;
 import com.google.gson.JsonElement;
 import com.google.gson.JsonObject;
-import net.fabricmc.fabric.api.client.rendering.v1.WorldRenderContext;
-import net.fabricmc.fabric.api.client.rendering.v1.WorldRenderEvents;
+import net.fabricmc.fabric.api.client.rendering.v1.world.WorldRenderContext;
+import net.fabricmc.fabric.api.client.rendering.v1.world.WorldRenderEvents;
 import net.fabricmc.fabric.api.event.player.UseBlockCallback;
-import net.minecraft.client.render.Camera;
 import net.minecraft.client.render.VertexConsumer;
 import net.minecraft.client.render.VertexConsumerProvider;
 import net.minecraft.client.util.math.MatrixStack;
@@ -138,49 +137,47 @@ public class BossWaypoints {
         return ActionResult.PASS;
     }
 
-    private static void renderThroughWall(WorldRenderContext worldRenderContext) {
+    private static void renderThroughWall(WorldRenderContext context) {
         if (!isInValidArea()) return;
 
-        Camera camera = worldRenderContext.camera();
-        Vec3d cameraPos = camera.getPos();
-        MatrixStack matrixStack = worldRenderContext.matrixStack();
-        if (matrixStack == null) return;
-        matrixStack.push();
-        matrixStack.translate(-cameraPos.x, -cameraPos.y, -cameraPos.z);
+        Vec3d camera = context.worldState().cameraRenderState.pos;
+        MatrixStack matrices = context.matrices();
+        if (matrices == null) return;
+        matrices.push();
+        matrices.translate(-camera.x, -camera.y, -camera.z);
 
-        VertexConsumerProvider consumers = worldRenderContext.consumers();
+        VertexConsumerProvider consumers = context.consumers();
         if (consumers == null) return;
         VertexConsumer consumer = consumers.getBuffer(RenderLayers.FILLED_LAYER);
 
         waypoints.forEach(waypoint -> {
             if (waypoint.isThroughWall()) {
-                waypoint.Render(consumer, matrixStack);
+                waypoint.Render(consumer, matrices);
             }
         });
-        matrixStack.pop();
+        matrices.pop();
 
     }
 
-    private static void render(WorldRenderContext worldRenderContext) {
+    private static void render(WorldRenderContext context) {
         if (!isInValidArea()) return;
+        Vec3d camera = context.worldState().cameraRenderState.pos;
 
-        Camera camera = worldRenderContext.camera();
-        Vec3d cameraPos = camera.getPos();
-        MatrixStack matrixStack = worldRenderContext.matrixStack();
-        if (matrixStack == null) return;
-        matrixStack.push();
-        matrixStack.translate(-cameraPos.x, -cameraPos.y, -cameraPos.z);
+        MatrixStack matrices = context.matrices();
+        if (matrices == null) return;
+        matrices.push();
+        matrices.translate(-camera.x, -camera.y, -camera.z);
 
-        VertexConsumerProvider consumers = worldRenderContext.consumers();
+        VertexConsumerProvider consumers = context.consumers();
         if (consumers == null) return;
         VertexConsumer consumer = consumers.getBuffer(RenderLayers.THROUGH_WALL_FILLED_LAYER);
 
         waypoints.forEach(waypoint -> {
             if (!waypoint.isThroughWall()) {
-                waypoint.Render(consumer, matrixStack);
+                waypoint.Render(consumer, matrices);
             }
         });
-        matrixStack.pop();
+        matrices.pop();
 
     }
 
diff --git a/src/main/java/blade/addon/features/dungeon/f7/DistanceToLedge.java b/src/main/java/blade/addon/features/dungeon/f7/DistanceToLedge.java
index f3d9605..43329b4 100644
--- a/src/main/java/blade/addon/features/dungeon/f7/DistanceToLedge.java
+++ b/src/main/java/blade/addon/features/dungeon/f7/DistanceToLedge.java
@@ -32,7 +32,7 @@ public class DistanceToLedge {
     private static boolean isAtYellow() {
         ClientPlayerEntity player = MinecraftClient.getInstance().player;
         if (player == null) return false;
-        return YELLOW_PAD.contains(player.getPos());
+        return YELLOW_PAD.contains(player.getEntityPos());
     }
 
     public static boolean display() {
diff --git a/src/main/java/blade/addon/features/dungeon/f7/RelicTimer.java b/src/main/java/blade/addon/features/dungeon/f7/RelicTimer.java
index c1a01a8..74939b1 100644
--- a/src/main/java/blade/addon/features/dungeon/f7/RelicTimer.java
+++ b/src/main/java/blade/addon/features/dungeon/f7/RelicTimer.java
@@ -11,11 +11,10 @@ import blade.addon.utils.rendering.RenderUtils;
 import blade.addon.utils.times.PersonalBests;
 import config.practical.hud.HUDComponent;
 import net.fabricmc.fabric.api.client.message.v1.ClientReceiveMessageEvents;
-import net.fabricmc.fabric.api.client.rendering.v1.WorldRenderEvents;
+import net.fabricmc.fabric.api.client.rendering.v1.world.WorldRenderEvents;
 import net.minecraft.client.MinecraftClient;
 import net.minecraft.client.gui.DrawContext;
 import net.minecraft.client.network.ClientPlayerEntity;
-import net.minecraft.client.render.Camera;
 import net.minecraft.client.render.VertexConsumer;
 import net.minecraft.client.render.VertexConsumerProvider;
 import net.minecraft.client.render.VertexRendering;
@@ -176,28 +175,26 @@ public class RelicTimer {
             return false;
         });
 
-        WorldRenderEvents.BEFORE_DEBUG_RENDER.register(worldRenderContext -> {
+        WorldRenderEvents.BEFORE_DEBUG_RENDER.register(context -> {
             if (pickedupRelic == null || !Floor7.renderRelicHighlight) return;
 
-            Camera camera = worldRenderContext.camera();
-            Vec3d cameraPos = camera.getPos();
+            Vec3d camera = context.worldState().cameraRenderState.pos;
+            MatrixStack matrices = context.matrices();
+            if (matrices == null) return;
+            matrices.push();
+            matrices.translate(-camera.x, -camera.y, -camera.z);
 
-            MatrixStack matrixStack = worldRenderContext.matrixStack();
-            if (matrixStack == null) return;
-            matrixStack.push();
-            matrixStack.translate(-cameraPos.x, -cameraPos.y, -cameraPos.z);
-
-            VertexConsumerProvider consumers = worldRenderContext.consumers();
+            VertexConsumerProvider consumers = context.consumers();
             if (consumers == null) return;
             VertexConsumer buffer = consumers.getBuffer(RenderLayers.FILLED_LAYER);
 
 
             Box box = pickedupRelic.box;
             float[] color = RenderUtils.toFloats(pickedupRelic.color);
-            VertexRendering.drawFilledBox(matrixStack, buffer, box.minX, box.minY, box.minZ, box.maxX, box.maxY, box.maxZ, color[0], color[1], color[2], color[3]);
+            VertexRendering.drawFilledBox(matrices, buffer, box.minX, box.minY, box.minZ, box.maxX, box.maxY, box.maxZ, color[0], color[1], color[2], color[3]);
 
 
-            matrixStack.pop();
+            matrices.pop();
         });
 
     }
diff --git a/src/main/java/blade/addon/features/dungeon/f7/location/PositionMessage.java b/src/main/java/blade/addon/features/dungeon/f7/location/PositionMessage.java
index 763d43a..0e31221 100644
--- a/src/main/java/blade/addon/features/dungeon/f7/location/PositionMessage.java
+++ b/src/main/java/blade/addon/features/dungeon/f7/location/PositionMessage.java
@@ -36,7 +36,7 @@ public class PositionMessage {
     public void tick(ClientPlayerEntity player) {
         if (sent) return;
         if (!inAValidSection()) return;
-        if (inRange(player.getPos())) {
+        if (inRange(player.getEntityPos())) {
             sent = true;
             if (Floor7.enablePositionalMessages) {
                 player.networkHandler.sendChatCommand("pc " + message);
diff --git a/src/main/java/blade/addon/features/dungeon/f7/location/PositionMessages.java b/src/main/java/blade/addon/features/dungeon/f7/location/PositionMessages.java
index 7b7231f..38719bb 100644
--- a/src/main/java/blade/addon/features/dungeon/f7/location/PositionMessages.java
+++ b/src/main/java/blade/addon/features/dungeon/f7/location/PositionMessages.java
@@ -7,11 +7,10 @@ import blade.addon.utils.dungeon.Phase;
 import blade.addon.utils.events.Events;
 import blade.addon.utils.rendering.RenderLayers;
 import net.fabricmc.fabric.api.client.event.lifecycle.v1.ClientTickEvents;
-import net.fabricmc.fabric.api.client.rendering.v1.WorldRenderContext;
-import net.fabricmc.fabric.api.client.rendering.v1.WorldRenderEvents;
+import net.fabricmc.fabric.api.client.rendering.v1.world.WorldRenderContext;
+import net.fabricmc.fabric.api.client.rendering.v1.world.WorldRenderEvents;
 import net.minecraft.client.MinecraftClient;
 import net.minecraft.client.network.ClientPlayerEntity;
-import net.minecraft.client.render.Camera;
 import net.minecraft.client.render.VertexConsumer;
 import net.minecraft.client.render.VertexConsumerProvider;
 import net.minecraft.client.render.VertexRendering;
@@ -97,31 +96,30 @@ public class PositionMessages {
         }
     }
 
-    private static void render(WorldRenderContext worldRenderContext) {
+    private static void render(WorldRenderContext context) {
         if (!Location.inDungeon() || !Debug.renderPositions) return;
 
-        Camera camera = worldRenderContext.camera();
-        Vec3d cameraPos = camera.getPos();
-        MatrixStack matrixStack = worldRenderContext.matrixStack();
-        if (matrixStack == null) return;
-        matrixStack.push();
-        matrixStack.translate(-cameraPos.x, -cameraPos.y, -cameraPos.z);
+        Vec3d camera = context.worldState().cameraRenderState.pos;
+        MatrixStack matrices = context.matrices();
+        if (matrices == null) return;
+        matrices.push();
+        matrices.translate(-camera.x, -camera.y, -camera.z);
 
-        VertexConsumerProvider consumers = worldRenderContext.consumers();
+        VertexConsumerProvider consumers = context.consumers();
         if (consumers == null) return;
         VertexConsumer consumer = consumers.getBuffer(RenderLayers.OUTLINE_LAYER);
+        MatrixStack.Entry entry = matrices.peek();
         for (PositionMessage positionMessage : positionMessages) {
             Box box = positionMessage.getBox();
-
             if (positionMessage.sent()) {
-                VertexRendering.drawBox(matrixStack, consumer, box, 0, 1, 0, 1);
+                VertexRendering.drawBox(entry, consumer, box, 0, 1, 0, 1);
             } else {
-                VertexRendering.drawBox(matrixStack, consumer, box, 1, 0, 0, 1);
+                VertexRendering.drawBox(entry, consumer, box, 1, 0, 0, 1);
             }
         }
 
 
-        matrixStack.pop();
+        matrices.pop();
 
     }
 }
diff --git a/src/main/java/blade/addon/features/dungeon/f7/terms/LeapNotification.java b/src/main/java/blade/addon/features/dungeon/f7/terms/LeapNotification.java
index bb08fb9..c58d60f 100644
--- a/src/main/java/blade/addon/features/dungeon/f7/terms/LeapNotification.java
+++ b/src/main/java/blade/addon/features/dungeon/f7/terms/LeapNotification.java
@@ -75,7 +75,7 @@ public class LeapNotification {
     }
 
     private static int getSpot(ClientPlayerEntity player) {
-        Vec3d pos = player.getPos();
+        Vec3d pos = player.getEntityPos();
         if (HEE2_BOX.contains(pos)) return 2;
         if (EE2_BOX.contains(pos)) return 2;
         else if (EE3_BOX.contains(pos)) return 3;
diff --git a/src/main/java/blade/addon/features/highlight/MimicHighlight.java b/src/main/java/blade/addon/features/highlight/MimicHighlight.java
new file mode 100644
index 0000000..3e42854
--- /dev/null
+++ b/src/main/java/blade/addon/features/highlight/MimicHighlight.java
@@ -0,0 +1,92 @@
+package blade.addon.features.highlight;
+
+import blade.addon.utils.Location;
+import blade.addon.utils.events.Events;
+import blade.addon.utils.rendering.RenderLayers;
+import blade.addon.utils.rendering.RenderUtils;
+import net.fabricmc.fabric.api.client.event.lifecycle.v1.ClientTickEvents;
+import net.fabricmc.fabric.api.client.rendering.v1.world.WorldRenderContext;
+import net.fabricmc.fabric.api.client.rendering.v1.world.WorldRenderEvents;
+import net.minecraft.block.entity.BlockEntity;
+import net.minecraft.block.entity.TrappedChestBlockEntity;
+import net.minecraft.client.render.VertexConsumer;
+import net.minecraft.client.render.VertexConsumerProvider;
+import net.minecraft.client.render.VertexRendering;
+import net.minecraft.client.util.math.MatrixStack;
+import net.minecraft.util.math.Box;
+import net.minecraft.util.math.Vec3d;
+
+import java.util.concurrent.ConcurrentLinkedQueue;
+
+public class MimicHighlight {
+
+    private static final Box box = new Box(0.0625, 0, 0.0625, 0.9375, 0.875, 0.9375);
+
+    private static final ConcurrentLinkedQueue<TrappedChestBlockEntity> mimicList = new ConcurrentLinkedQueue<>();
+
+    public static void init() {
+
+        Events.ON_LOCATION_CHANGE.register(location -> {
+            mimicList.clear();
+        });
+
+       Events.ON_BLOCK_ENTITY.register(blockEntity -> {
+          if (!Location.inDungeon()) return;
+          if (blockEntity instanceof TrappedChestBlockEntity mimic) {
+              mimicList.add(mimic);
+          }
+       });
+
+       ClientTickEvents.END_CLIENT_TICK.register(client -> {mimicList.removeIf(BlockEntity::isRemoved);});
+
+        WorldRenderEvents.AFTER_ENTITIES.register(MimicHighlight::renderFilled);
+        WorldRenderEvents.AFTER_ENTITIES.register(MimicHighlight::renderOutline);
+    }
+
+    private static void renderFilled(WorldRenderContext context) {
+        if (!MobHighlight.highlightMimicChests) return;
+        if (!MobHighlight.renderFilled()) return;
+        Vec3d camera = context.worldState().cameraRenderState.pos;
+        MatrixStack matrices = context.matrices();
+        if (matrices == null) return;
+        matrices.push();
+        matrices.translate(-camera.x, -camera.y, -camera.z);
+
+        VertexConsumerProvider consumers = context.consumers();
+        if (consumers == null) return;
+        VertexConsumer filledConsumer = consumers.getBuffer(RenderLayers.FILLED_ENTITY_LAYER);
+
+        float[] rgba = RenderUtils.toFloats(MobHighlight.mimicFilledColor);
+        mimicList.forEach(blockEntity -> {
+            Box renderBox = box.offset(blockEntity.getPos());
+            VertexRendering.drawFilledBox(matrices, filledConsumer, renderBox.minX, renderBox.minY, renderBox.minZ, renderBox.maxX, renderBox.maxY, renderBox.maxZ, rgba[0], rgba[1], rgba[2], rgba[3]);
+        });
+
+        matrices.pop();
+
+    }
+
+    private static void renderOutline(WorldRenderContext context) {
+        if (!MobHighlight.highlightMimicChests) return;
+        if (!MobHighlight.renderOutline()) return;
+        Vec3d camera = context.worldState().cameraRenderState.pos;
+        MatrixStack matrices = context.matrices();
+        if (matrices == null) return;
+        matrices.push();
+        matrices.translate(-camera.x, -camera.y, -camera.z);
+
+        VertexConsumerProvider consumers = context.consumers();
+        if (consumers == null) return;
+        VertexConsumer outlineConsumer = consumers.getBuffer(RenderLayers.getOutline(MobHighlight.outlineWidth));
+
+        MatrixStack.Entry entry = matrices.peek();
+        float[] rgba = RenderUtils.toFloats(MobHighlight.mimicOutlineColor);
+        mimicList.forEach(blockEntity -> {
+            Box renderBox = box.offset(blockEntity.getPos());
+            VertexRendering.drawBox(entry, outlineConsumer, renderBox, rgba[0], rgba[1], rgba[2], rgba[3]);
+        });
+
+        matrices.pop();
+
+    }
+}
diff --git a/src/main/java/blade/addon/features/highlight/MobHighlight.java b/src/main/java/blade/addon/features/highlight/MobHighlight.java
index 327729c..0b33775 100644
--- a/src/main/java/blade/addon/features/highlight/MobHighlight.java
+++ b/src/main/java/blade/addon/features/highlight/MobHighlight.java
@@ -9,13 +9,12 @@ import blade.addon.utils.rendering.RenderUtils;
 import config.practical.manager.ConfigValue;
 import net.fabricmc.fabric.api.client.event.lifecycle.v1.ClientEntityEvents;
 import net.fabricmc.fabric.api.client.event.lifecycle.v1.ClientTickEvents;
-import net.fabricmc.fabric.api.client.rendering.v1.WorldRenderContext;
-import net.fabricmc.fabric.api.client.rendering.v1.WorldRenderEvents;
+import net.fabricmc.fabric.api.client.rendering.v1.world.WorldRenderContext;
+import net.fabricmc.fabric.api.client.rendering.v1.world.WorldRenderEvents;
 import net.minecraft.client.MinecraftClient;
 import net.minecraft.client.network.ClientPlayNetworkHandler;
 import net.minecraft.client.network.ClientPlayerEntity;
 import net.minecraft.client.network.PlayerListEntry;
-import net.minecraft.client.render.Camera;
 import net.minecraft.client.render.VertexConsumer;
 import net.minecraft.client.render.VertexConsumerProvider;
 import net.minecraft.client.render.VertexRendering;
@@ -254,7 +253,7 @@ public class MobHighlight {
         Entity closest = null;
         double smallestDistance = maxDistance;
         double maxY = armorStand.getY();
-        Box boundingBox = Box.of(armorStand.getPos().subtract(0, 0.5, 0), 2, 3, 2);
+        Box boundingBox = Box.of(armorStand.getEntityPos().subtract(0, 0.5, 0), 2, 3, 2);
 
 
         ArrayList<Entity> meetsRequirements = new ArrayList<>();
@@ -273,7 +272,7 @@ public class MobHighlight {
             if (!requirements.test(current)) continue;
             if (current.getY() > maxY) continue;
 
-            double distance = armorStand.getPos().distanceTo(current.getPos());
+            double distance = armorStand.getEntityPos().distanceTo(current.getEntityPos());
             if (smallestDistance > distance) {
                 closest = current;
                 smallestDistance = distance;
@@ -314,7 +313,7 @@ public class MobHighlight {
 
             //this is a hack which will fail if someone has a really old bugged ign that includes a space
             if (entry != null) {
-                String name = entry.getProfile().getName();
+                String name = entry.getProfile().name();
                 return !name.isEmpty() && !name.contains(" ");
             }
         }
@@ -413,25 +412,25 @@ public class MobHighlight {
         return box;
     }
 
-    private static void renderFilled(WorldRenderContext worldRenderContext) {
+    private static void renderFilled(WorldRenderContext context) {
         if (!mobHighlight || dontRenderHighlight) return;
         if (!renderFilled()) return;
-        Camera camera = worldRenderContext.camera();
-        Vec3d cameraPos = camera.getPos();
-        MatrixStack matrixStack = worldRenderContext.matrixStack();
-        if (matrixStack == null) return;
-        matrixStack.push();
-        matrixStack.translate(-cameraPos.x, -cameraPos.y, -cameraPos.z);
-
-        VertexConsumerProvider consumers = worldRenderContext.consumers();
+        Vec3d camera = context.worldState().cameraRenderState.pos;
+        MatrixStack matrices = context.matrices();
+        if (matrices == null) return;
+        matrices.push();
+        matrices.translate(-camera.x, -camera.y, -camera.z);
+
+        VertexConsumerProvider consumers = context.consumers();
         if (consumers == null) return;
         VertexConsumer filledConsumer = consumers.getBuffer(RenderLayers.FILLED_ENTITY_LAYER);
 
+        double tickProgress = MinecraftClient.getInstance().getRenderTickCounter().getTickProgress(false);
+
         trackedMobs.forEach((entity, mobType) -> {
 
             if (entity.isInvisible() && MobHighlight.dontShowInvisibleMobs && entity instanceof PlayerEntity) return;
 
-            double tickProgress = worldRenderContext.tickCounter().getTickProgress(false);
             double x = MathHelper.lerp(tickProgress, entity.lastRenderX, entity.getX());
             double y = MathHelper.lerp(tickProgress, entity.lastRenderY, entity.getY());
             double z = MathHelper.lerp(tickProgress, entity.lastRenderZ, entity.getZ());
@@ -441,34 +440,35 @@ public class MobHighlight {
 
             int filledColor = MobHighlight.getFilledColor(entity);
             float[] rgba = RenderUtils.toFloats(filledColor);
-            VertexRendering.drawFilledBox(matrixStack, filledConsumer, box.minX, box.minY, box.minZ, box.maxX, box.maxY, box.maxZ, rgba[0], rgba[1], rgba[2], rgba[3]);
+            VertexRendering.drawFilledBox(matrices, filledConsumer, box.minX, box.minY, box.minZ, box.maxX, box.maxY, box.maxZ, rgba[0], rgba[1], rgba[2], rgba[3]);
         });
 
 
-        matrixStack.pop();
+        matrices.pop();
 
     }
 
-    private static void renderOutline(WorldRenderContext worldRenderContext) {
+    private static void renderOutline(WorldRenderContext context) {
         if (!mobHighlight || dontRenderHighlight) return;
         if (!renderOutline()) return;
-        Camera camera = worldRenderContext.camera();
-        Vec3d cameraPos = camera.getPos();
-        MatrixStack matrixStack = worldRenderContext.matrixStack();
-        if (matrixStack == null) return;
-        matrixStack.push();
-        matrixStack.translate(-cameraPos.x, -cameraPos.y, -cameraPos.z);
-
-        VertexConsumerProvider consumers = worldRenderContext.consumers();
+        Vec3d camera = context.worldState().cameraRenderState.pos;
+        MatrixStack matrices = context.matrices();
+        if (matrices == null) return;
+        matrices.push();
+        matrices.translate(-camera.x, -camera.y, -camera.z);
+
+        VertexConsumerProvider consumers = context.consumers();
         if (consumers == null) return;
         VertexConsumer outlineConsumer = consumers.getBuffer(RenderLayers.getOutline(outlineWidth));
 
+        double tickProgress = MinecraftClient.getInstance().getRenderTickCounter().getTickProgress(false);
+
+        MatrixStack.Entry entry = matrices.peek();
 
         trackedMobs.forEach((entity, mobType) -> {
 
             if (entity.isInvisible() && MobHighlight.dontShowInvisibleMobs && entity instanceof PlayerEntity) return;
 
-            double tickProgress = worldRenderContext.tickCounter().getTickProgress(false);
             double x = MathHelper.lerp(tickProgress, entity.lastRenderX, entity.getX());
             double y = MathHelper.lerp(tickProgress, entity.lastRenderY, entity.getY());
             double z = MathHelper.lerp(tickProgress, entity.lastRenderZ, entity.getZ());
@@ -478,11 +478,11 @@ public class MobHighlight {
 
             int outlineColor = MobHighlight.getOutlineColor(entity);
             float[] rgba = RenderUtils.toFloats(outlineColor);
-            VertexRendering.drawBox(matrixStack, outlineConsumer, box, rgba[0], rgba[1], rgba[2], rgba[3]);
+            VertexRendering.drawBox(entry, outlineConsumer, box, rgba[0], rgba[1], rgba[2], rgba[3]);
         });
 
 
-        matrixStack.pop();
+        matrices.pop();
 
     }
 }
diff --git a/src/main/java/blade/addon/features/other/InventoryButton.java b/src/main/java/blade/addon/features/other/InventoryButton.java
index c53e00b..cf83d33 100644
--- a/src/main/java/blade/addon/features/other/InventoryButton.java
+++ b/src/main/java/blade/addon/features/other/InventoryButton.java
@@ -38,7 +38,7 @@ public class InventoryButton {
         context.drawText(textRenderer, "" + index, x + (SIZE - center) / 2 + 1, y + (SIZE - textRenderer.fontHeight) / 2 + 1, 0xffffffff, true);
     }
 
-    public void onClick(double mouseX, double mouseY, int button) {
+    public void onClick(double mouseX, double mouseY) {
         String str = command.get();
         if (str == null ||str.isEmpty()) return;
         Misc.executeCommand(str);
@@ -48,10 +48,10 @@ public class InventoryButton {
         return mouseX >= x && mouseX <= x + SIZE && mouseY >= y && mouseY <= y + SIZE;
     }
 
-    public static void parseClicks(double mouseX, double mouseY, int button) {
+    public static void parseClicks(double mouseX, double mouseY) {
         for (InventoryButton inventoryButton : inventoryButtons) {
             if (inventoryButton.inBounds(mouseX, mouseY)) {
-                inventoryButton.onClick(mouseX, mouseY, button);
+                inventoryButton.onClick(mouseX, mouseY);
             }
         }
     }
diff --git a/src/main/java/blade/addon/features/other/PracticeSS.java b/src/main/java/blade/addon/features/other/PracticeSS.java
index bc7be4e..0b781f2 100644
--- a/src/main/java/blade/addon/features/other/PracticeSS.java
+++ b/src/main/java/blade/addon/features/other/PracticeSS.java
@@ -9,13 +9,12 @@ import blade.addon.utils.rendering.RenderLayers;
 import blade.addon.utils.rendering.RenderUtils;
 import blade.addon.utils.times.PersonalBests;
 import net.fabricmc.fabric.api.client.event.lifecycle.v1.ClientTickEvents;
-import net.fabricmc.fabric.api.client.rendering.v1.WorldRenderContext;
-import net.fabricmc.fabric.api.client.rendering.v1.WorldRenderEvents;
+import net.fabricmc.fabric.api.client.rendering.v1.world.WorldRenderContext;
+import net.fabricmc.fabric.api.client.rendering.v1.world.WorldRenderEvents;
 import net.minecraft.block.Block;
 import net.minecraft.block.BlockState;
 import net.minecraft.block.Blocks;
 import net.minecraft.client.MinecraftClient;
-import net.minecraft.client.render.Camera;
 import net.minecraft.client.render.VertexConsumer;
 import net.minecraft.client.render.VertexConsumerProvider;
 import net.minecraft.client.render.VertexRendering;
@@ -340,18 +339,15 @@ public class PracticeSS {
         return endIndex - ticksLeft / getDelay();
     }
 
-    private static void render(WorldRenderContext worldRenderContext) {
+    private static void render(WorldRenderContext context) {
         if (!started) return;
+        Vec3d camera = context.worldState().cameraRenderState.pos;
+        MatrixStack matrices = context.matrices();
+        if (matrices == null) return;
+        matrices.push();
+        matrices.translate(-camera.x, -camera.y, -camera.z);
 
-        Camera camera = worldRenderContext.camera();
-        Vec3d cameraPos = camera.getPos();
-
-        MatrixStack matrixStack = worldRenderContext.matrixStack();
-        if (matrixStack == null) return;
-        matrixStack.push();
-        matrixStack.translate(-cameraPos.x, -cameraPos.y, -cameraPos.z);
-
-        VertexConsumerProvider consumers = worldRenderContext.consumers();
+        VertexConsumerProvider consumers = context.consumers();
         if (consumers == null) return;
         VertexConsumer buffer = consumers.getBuffer(RenderLayers.FILLED_LAYER);
 
@@ -359,15 +355,15 @@ public class PracticeSS {
         if (showingPattern) {
             if (!ExtraOptions.realisticDelay || totalTicks - ticksLeft > START_WAIT_DURATION || endIndex != 2) {
                 int lastIndex = getLastDisplayIndex();
-                renderButtons(matrixStack, buffer, 0, lastIndex);
-                renderBackground(matrixStack, buffer, lastIndex - 1);
+                renderButtons(matrices, buffer, 0, lastIndex);
+                renderBackground(matrices, buffer, lastIndex - 1);
             }
         } else {
-            renderButtons(matrixStack, buffer, currentIndex, endIndex);
+            renderButtons(matrices, buffer, currentIndex, endIndex);
         }
 
 
-        matrixStack.pop();
+        matrices.pop();
     }
 
     private static void renderButtons(MatrixStack matrixStack, VertexConsumer buffer, int start, int end) {
diff --git a/src/main/java/blade/addon/mixin/BlockEntityRenderMixin.java b/src/main/java/blade/addon/mixin/BlockEntityRenderMixin.java
deleted file mode 100644
index 4ee048e..0000000
--- a/src/main/java/blade/addon/mixin/BlockEntityRenderMixin.java
+++ /dev/null
@@ -1,43 +0,0 @@
-package blade.addon.mixin;
-
-import blade.addon.features.highlight.MobHighlight;
-import blade.addon.utils.Location;
-import blade.addon.utils.rendering.RenderLayers;
-import blade.addon.utils.rendering.RenderUtils;
-import net.minecraft.block.entity.BlockEntity;
-import net.minecraft.block.entity.TrappedChestBlockEntity;
-import net.minecraft.client.render.VertexConsumer;
-import net.minecraft.client.render.VertexConsumerProvider;
-import net.minecraft.client.render.VertexRendering;
-import net.minecraft.client.render.block.entity.BlockEntityRenderDispatcher;
-import net.minecraft.client.util.math.MatrixStack;
-import net.minecraft.util.math.Box;
-import org.spongepowered.asm.mixin.Mixin;
-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.callback.CallbackInfo;
-
-@Mixin(BlockEntityRenderDispatcher.class)
-public class BlockEntityRenderMixin {
-
-    @Unique
-    private static final Box box = new Box(0.0625, 0, 0.0625, 0.9375, 0.875, 0.9375);
-
-    @Inject(method = "render(Lnet/minecraft/block/entity/BlockEntity;FLnet/minecraft/client/util/math/MatrixStack;Lnet/minecraft/client/render/VertexConsumerProvider;)V", at = @At("TAIL"))
-    private static<E extends BlockEntity> void render(E blockEntity, float tickProgress, MatrixStack matrices, VertexConsumerProvider vertexConsumers, CallbackInfo ci) {
-        if (blockEntity instanceof TrappedChestBlockEntity) {
-            if (!Location.inDungeon() || !MobHighlight.highlightMimicChests) return;
-            if (MobHighlight.renderFilled()) {
-                VertexConsumer buffer = vertexConsumers.getBuffer(RenderLayers.FILLED_ENTITY_LAYER);
-                float[] rgba = RenderUtils.toFloats(MobHighlight.mimicFilledColor);
-                VertexRendering.drawFilledBox(matrices, buffer, box.minX, box.minY, box.minZ, box.maxX, box.maxY, box.maxZ, rgba[0], rgba[1], rgba[2], rgba[3]);
-            }
-            if (MobHighlight.renderOutline()) {
-                VertexConsumer buffer = vertexConsumers.getBuffer(RenderLayers.OUTLINE_ENTITY_LAYER);
-                float[] rgba = RenderUtils.toFloats(MobHighlight.mimicOutlineColor);
-                VertexRendering.drawBox(matrices, buffer, box, rgba[0], rgba[1], rgba[2], rgba[3]);
-            }
-        }
-    }
-}
diff --git a/src/main/java/blade/addon/mixin/ChunkMixin.java b/src/main/java/blade/addon/mixin/ChunkMixin.java
new file mode 100644
index 0000000..6c2bf2f
--- /dev/null
+++ b/src/main/java/blade/addon/mixin/ChunkMixin.java
@@ -0,0 +1,20 @@
+package blade.addon.mixin;
+
+import blade.addon.utils.events.Events;
+import net.minecraft.block.entity.BlockEntity;
+import net.minecraft.world.chunk.WorldChunk;
+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;
+
+@Mixin(WorldChunk.class)
+public abstract class ChunkMixin {
+
+    @Inject(method = "setBlockEntity", at=@At("HEAD"))
+    public void onBlockEntity(BlockEntity blockEntity, CallbackInfo ci) {
+        Events.ON_BLOCK_ENTITY.invoke(blockEntityEvent -> blockEntityEvent.on(blockEntity));
+
+    }
+
+}
diff --git a/src/main/java/blade/addon/mixin/ServerTickMixin.java b/src/main/java/blade/addon/mixin/ClientConnectionMixin.java
similarity index 98%
rename from src/main/java/blade/addon/mixin/ServerTickMixin.java
rename to src/main/java/blade/addon/mixin/ClientConnectionMixin.java
index ab33832..3c3f6fb 100644
--- a/src/main/java/blade/addon/mixin/ServerTickMixin.java
+++ b/src/main/java/blade/addon/mixin/ClientConnectionMixin.java
@@ -16,7 +16,7 @@ import org.spongepowered.asm.mixin.injection.Inject;
 import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
 
 @Mixin(ClientConnection.class)
-public class ServerTickMixin {
+public class ClientConnectionMixin {
 
     @Inject(method = "channelRead0(Lio/netty/channel/ChannelHandlerContext;Lnet/minecraft/network/packet/Packet;)V", at = @At("HEAD"), order=0, cancellable = true)
     private void channelRead0(ChannelHandlerContext channelHandlerContext, Packet<?> packet, CallbackInfo ci) {
diff --git a/src/main/java/blade/addon/mixin/NetworkMixin.java b/src/main/java/blade/addon/mixin/ClientPlayNetworkHandlerMixin.java
similarity index 90%
rename from src/main/java/blade/addon/mixin/NetworkMixin.java
rename to src/main/java/blade/addon/mixin/ClientPlayNetworkHandlerMixin.java
index 1044583..c405611 100644
--- a/src/main/java/blade/addon/mixin/NetworkMixin.java
+++ b/src/main/java/blade/addon/mixin/ClientPlayNetworkHandlerMixin.java
@@ -1,5 +1,6 @@
 package blade.addon.mixin;
 
+import blade.addon.features.dungeon.DeathTickTimer;
 import blade.addon.utils.Debug;
 import blade.addon.utils.Misc;
 import blade.addon.utils.Scheduler;
@@ -18,6 +19,7 @@ import net.minecraft.network.packet.s2c.play.EntitySpawnS2CPacket;
 import net.minecraft.network.packet.s2c.play.EntityTrackerUpdateS2CPacket;
 import net.minecraft.network.packet.s2c.play.PlaySoundS2CPacket;
 import net.minecraft.network.packet.s2c.play.PlayerListS2CPacket;
+import net.minecraft.network.packet.s2c.play.PlayerPositionLookS2CPacket;
 import net.minecraft.network.packet.s2c.play.TeamS2CPacket;
 import net.minecraft.scoreboard.Team;
 import net.minecraft.sound.SoundEvent;
@@ -31,7 +33,7 @@ import org.spongepowered.asm.mixin.injection.Inject;
 import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
 
 @Mixin(ClientPlayNetworkHandler.class)
-public class NetworkMixin {
+public class ClientPlayNetworkHandlerMixin {
 
 
     @Shadow
@@ -68,7 +70,7 @@ public class NetworkMixin {
     }
 
     @Inject(method = "onPlaySound", at = @At(value = "HEAD"), cancellable = true)
-    private void onTeam(PlaySoundS2CPacket packet, CallbackInfo ci) {
+    private void onSound(PlaySoundS2CPacket packet, CallbackInfo ci) {
         float volume = packet.getVolume();
         float pitch = packet.getPitch();
         SoundEvent event = packet.getSound().value();
@@ -101,6 +103,11 @@ public class NetworkMixin {
 
     }
 
+    @Inject(method = "onPlayerPositionLook", at = @At("HEAD"))
+    private void onPlayerLook(PlayerPositionLookS2CPacket packet, CallbackInfo ci) {
+        DeathTickTimer.onTeleport(packet.change().position());
+    }
+
     @Unique
     private static boolean isARealPlayer(Entity entity) {
         if (entity instanceof PlayerEntity player) {
@@ -114,7 +121,7 @@ public class NetworkMixin {
 
             //this is a hack which will fail if someone has a really old bugged ign that includes a space
             if (entry != null) {
-                String name = entry.getProfile().getName();
+                String name = entry.getProfile().name();
                 return !name.isEmpty() && !name.contains(" ");
             }
         }
diff --git a/src/main/java/blade/addon/mixin/RenderMixin.java b/src/main/java/blade/addon/mixin/EntityRenderManagerMixin.java
similarity index 92%
rename from src/main/java/blade/addon/mixin/RenderMixin.java
rename to src/main/java/blade/addon/mixin/EntityRenderManagerMixin.java
index b656d2d..b8fa133 100644
--- a/src/main/java/blade/addon/mixin/RenderMixin.java
+++ b/src/main/java/blade/addon/mixin/EntityRenderManagerMixin.java
@@ -7,7 +7,7 @@ import blade.addon.utils.config.values.ExtraOptions;
 import net.minecraft.client.MinecraftClient;
 import net.minecraft.client.network.ClientPlayerEntity;
 import net.minecraft.client.render.Frustum;
-import net.minecraft.client.render.entity.EntityRenderDispatcher;
+import net.minecraft.client.render.entity.EntityRenderManager;
 import net.minecraft.entity.Entity;
 import net.minecraft.entity.ItemEntity;
 import net.minecraft.entity.player.PlayerEntity;
@@ -16,8 +16,8 @@ import org.spongepowered.asm.mixin.injection.At;
 import org.spongepowered.asm.mixin.injection.Inject;
 import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
 
-@Mixin(EntityRenderDispatcher.class)
-public class RenderMixin {
+@Mixin(EntityRenderManager.class)
+public class EntityRenderManagerMixin {
 
     @Inject(method = "shouldRender", at = @At("TAIL"), cancellable = true)
     private <E extends Entity> void shouldRender(E entity, Frustum frustum, double x, double y, double z, CallbackInfoReturnable<Boolean> cir) {
diff --git a/src/main/java/blade/addon/mixin/EntityRendererMixin.java b/src/main/java/blade/addon/mixin/EntityRendererMixin.java
index 3d325ab..83e2dc2 100644
--- a/src/main/java/blade/addon/mixin/EntityRendererMixin.java
+++ b/src/main/java/blade/addon/mixin/EntityRendererMixin.java
@@ -1,10 +1,15 @@
 package blade.addon.mixin;
 
+import blade.addon.utils.Misc;
 import blade.addon.utils.config.values.Dungeons;
-import net.minecraft.client.render.VertexConsumerProvider;
+import blade.addon.utils.config.values.ExtraOptions;
+import net.minecraft.client.render.command.OrderedRenderCommandQueue;
 import net.minecraft.client.render.entity.EntityRenderer;
 import net.minecraft.client.render.entity.state.EntityRenderState;
+import net.minecraft.client.render.state.CameraRenderState;
 import net.minecraft.client.util.math.MatrixStack;
+import net.minecraft.entity.Entity;
+import net.minecraft.entity.player.PlayerEntity;
 import net.minecraft.text.Text;
 import org.spongepowered.asm.mixin.Mixin;
 import org.spongepowered.asm.mixin.injection.At;
@@ -12,13 +17,24 @@ import org.spongepowered.asm.mixin.injection.Inject;
 import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
 
 @Mixin(EntityRenderer.class)
-public class EntityRendererMixin<S extends EntityRenderState> {
+public class EntityRendererMixin<T extends Entity, S extends EntityRenderState> {
 
     @Inject(method = "renderLabelIfPresent", at=@At("HEAD"), cancellable = true)
-    private void hideNameTag(S state, Text text, MatrixStack matrices, VertexConsumerProvider vertexConsumers, int light, CallbackInfo ci) {
+    private void hideNameTag(S state, MatrixStack matrices, OrderedRenderCommandQueue queue, CameraRenderState cameraRenderState, CallbackInfo ci) {
+        Text text = state.displayName;
         if (!Dungeons.hideBlazeNameTag || text == null) return;
         String string = text.getString();
         if (string.contains("Blaze")) ci.cancel();
 
     }
+
+    @Inject(method = "updateRenderState", at = @At("TAIL"))
+    public void hideFire(T entity, S state, float tickProgress, CallbackInfo ci) {
+
+        if (entity instanceof PlayerEntity player && ExtraOptions.hideFireInf5) {
+            if (Misc.isClientPlayer(player)) {
+                state.onFire = false;
+            }
+        }
+    }
 }
diff --git a/src/main/java/blade/addon/mixin/FireMixin.java b/src/main/java/blade/addon/mixin/FireMixin.java
deleted file mode 100644
index a1cabe1..0000000
--- a/src/main/java/blade/addon/mixin/FireMixin.java
+++ /dev/null
@@ -1,26 +0,0 @@
-package blade.addon.mixin;
-
-import blade.addon.utils.Misc;
-import blade.addon.utils.config.values.ExtraOptions;
-import net.minecraft.client.render.entity.EntityRenderer;
-import net.minecraft.client.render.entity.state.EntityRenderState;
-import net.minecraft.entity.Entity;
-import net.minecraft.entity.player.PlayerEntity;
-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;
-
-@Mixin(EntityRenderer.class)
-public class FireMixin<T extends Entity, S extends EntityRenderState> {
-
-    @Inject(method = "updateRenderState", at = @At("TAIL"))
-    public void test(T entity, S state, float tickProgress, CallbackInfo ci) {
-
-        if (entity instanceof PlayerEntity player && ExtraOptions.hideFireInf5) {
-            if (Misc.isClientPlayer(player)) {
-                state.onFire = false;
-            }
-        }
-    }
-}
diff --git a/src/main/java/blade/addon/mixin/InventoryScreenMixin.java b/src/main/java/blade/addon/mixin/InventoryScreenMixin.java
index c37675f..ecca2be 100644
--- a/src/main/java/blade/addon/mixin/InventoryScreenMixin.java
+++ b/src/main/java/blade/addon/mixin/InventoryScreenMixin.java
@@ -1,6 +1,7 @@
 package blade.addon.mixin;
 
 import blade.addon.features.other.InventoryButton;
+import net.minecraft.client.gui.Click;
 import net.minecraft.client.gui.DrawContext;
 import net.minecraft.client.gui.screen.ingame.InventoryScreen;
 import net.minecraft.client.gui.screen.ingame.RecipeBookScreen;
@@ -20,12 +21,6 @@ import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
 @Mixin(InventoryScreen.class)
 public abstract class InventoryScreenMixin extends RecipeBookScreen<PlayerScreenHandler> {
 
-    @Shadow
-    private float mouseX;
-
-    @Shadow
-    private float mouseY;
-
     @Final
     @Shadow
     private StatusEffectsDisplay statusEffectsDisplay;
@@ -46,9 +41,9 @@ public abstract class InventoryScreenMixin extends RecipeBookScreen<PlayerScreen
     }
 
     @Override
-    public boolean mouseClicked(double mouseX, double mouseY, int button) {
-        InventoryButton.parseClicks(mouseX - x, mouseY - y, button);
-        return super.mouseClicked(mouseX, mouseY, button);
+    public boolean mouseClicked(Click click, boolean doubled) {
+        InventoryButton.parseClicks(click.x() - x, click.y() - y);
+        return super.mouseClicked(click, doubled);
     }
 
     @Inject(method = "drawForeground", at = @At("HEAD"), cancellable = true)
diff --git a/src/main/java/blade/addon/mixin/InventoryMixin.java b/src/main/java/blade/addon/mixin/PlayerInventoryMixin.java
similarity index 85%
rename from src/main/java/blade/addon/mixin/InventoryMixin.java
rename to src/main/java/blade/addon/mixin/PlayerInventoryMixin.java
index 4c6e03a..80a4e90 100644
--- a/src/main/java/blade/addon/mixin/InventoryMixin.java
+++ b/src/main/java/blade/addon/mixin/PlayerInventoryMixin.java
@@ -9,10 +9,10 @@ import org.spongepowered.asm.mixin.injection.Inject;
 import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
 
 @Mixin(PlayerInventory.class)
-public class InventoryMixin {
+public class PlayerInventoryMixin {
 
     @Inject(method = "setStack", at = @At("HEAD"))
-    private void channelRead0(int slot, ItemStack stack, CallbackInfo ci) {
+    private void setStack(int slot, ItemStack stack, CallbackInfo ci) {
         if (Events.ON_SLOT_CHANGE.hasListeners()) {
             Events.ON_SLOT_CHANGE.listeners.forEach(slotChangeEvent -> slotChangeEvent.onSlotChange(slot, stack));
         }
diff --git a/src/main/java/blade/addon/mixin/PositionMixin.java b/src/main/java/blade/addon/mixin/PositionMixin.java
deleted file mode 100644
index f2f0b29..0000000
--- a/src/main/java/blade/addon/mixin/PositionMixin.java
+++ /dev/null
@@ -1,21 +0,0 @@
-package blade.addon.mixin;
-
-import blade.addon.features.dungeon.DeathTickTimer;
-import net.minecraft.client.network.ClientPlayNetworkHandler;
-import net.minecraft.network.packet.s2c.play.PlayerPositionLookS2CPacket;
-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;
-
-@Mixin(ClientPlayNetworkHandler.class)
-public class PositionMixin {
-
-    @Inject(method = "onPlayerPositionLook", at = @At("HEAD"))
-    private void channelRead0(PlayerPositionLookS2CPacket packet, CallbackInfo ci) {
-        DeathTickTimer.onTeleport(packet.change().position());
-    }
-
-
-
-}
diff --git a/src/main/java/blade/addon/mixin/ArrowMixin.java b/src/main/java/blade/addon/mixin/StuckArrowsFeatureRendererMixin.java
similarity index 93%
rename from src/main/java/blade/addon/mixin/ArrowMixin.java
rename to src/main/java/blade/addon/mixin/StuckArrowsFeatureRendererMixin.java
index 58ce169..910bce5 100644
--- a/src/main/java/blade/addon/mixin/ArrowMixin.java
+++ b/src/main/java/blade/addon/mixin/StuckArrowsFeatureRendererMixin.java
@@ -8,7 +8,7 @@ import org.spongepowered.asm.mixin.injection.Inject;
 import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
 
 @Mixin(StuckArrowsFeatureRenderer.class)
-public class ArrowMixin {
+public class StuckArrowsFeatureRendererMixin {
 
     @Inject(method = "getObjectCount", at=@At("TAIL"), cancellable = true)
     public void shouldRender(CallbackInfoReturnable<Integer> cir) {
diff --git a/src/main/java/blade/addon/utils/Constants.java b/src/main/java/blade/addon/utils/Constants.java
index bd21197..5ee8630 100644
--- a/src/main/java/blade/addon/utils/Constants.java
+++ b/src/main/java/blade/addon/utils/Constants.java
@@ -10,7 +10,7 @@ public class Constants {
     public static final int TEXT_HEIGHT = 10;
 
     public static final int GREEN_COLOR = 0xff00ff00;
-    public static final int ORANGE_COLOR = 0xffDAA06D;
+    public static final int ORANGE_COLOR = 0xffffaa00;
     public static final int RED_COLOR = 0xffff0000;
 
     public static final int FAIL = 0;
diff --git a/src/main/java/blade/addon/utils/Keybinds.java b/src/main/java/blade/addon/utils/Keybinds.java
index d075b80..3e3f048 100644
--- a/src/main/java/blade/addon/utils/Keybinds.java
+++ b/src/main/java/blade/addon/utils/Keybinds.java
@@ -13,14 +13,13 @@ import net.minecraft.component.type.LoreComponent;
 import net.minecraft.component.type.NbtComponent;
 import net.minecraft.item.ItemStack;
 import net.minecraft.text.Text;
+import net.minecraft.util.Identifier;
 import org.lwjgl.glfw.GLFW;
 
 import java.util.List;
 
 public class Keybinds {
 
-    private static final String CATEGORY = Constants.NAMESPACE;
-
     private static KeyBinding openConfig;
     private static KeyBinding getItemLore;
     private static KeyBinding getItemCustomData;
@@ -28,23 +27,25 @@ public class Keybinds {
 
     public static void init() {
 
+        KeyBinding.Category category = KeyBinding.Category.create(Identifier.of(Constants.NAMESPACE));
+
         openConfig = KeyBindingHelper.registerKeyBinding(new KeyBinding(
                 "opens Config",
                 InputUtil.Type.KEYSYM,
                 GLFW.GLFW_KEY_RIGHT_SHIFT,
-                CATEGORY));
+                category));
 
         getItemLore = KeyBindingHelper.registerKeyBinding(new KeyBinding(
                 "Grabs the items lore",
                 InputUtil.Type.KEYSYM,
                 GLFW.GLFW_KEY_UNKNOWN,
-                CATEGORY));
+                category));
 
         getItemCustomData = KeyBindingHelper.registerKeyBinding(new KeyBinding(
                 "Grabs the items custom data",
                 InputUtil.Type.KEYSYM,
                 GLFW.GLFW_KEY_UNKNOWN,
-                CATEGORY));
+                category));
 
         ClientTickEvents.END_CLIENT_TICK.register(Keybinds::checkInputs);
     }
diff --git a/src/main/java/blade/addon/utils/config/Config.java b/src/main/java/blade/addon/utils/config/Config.java
index 73afe86..9fd2bb9 100644
--- a/src/main/java/blade/addon/utils/config/Config.java
+++ b/src/main/java/blade/addon/utils/config/Config.java
@@ -61,7 +61,7 @@ public class Config {
         hidePlayers.add(new ConfigBool(Text.literal("Hide at SS"), () -> Dungeons.hideAtSS, bool -> Dungeons.hideAtSS = bool));
         hidePlayers.add(new ConfigBool(Text.literal("SS only hides before terms"), () -> Dungeons.hideBeforeTermsOnly, bool -> Dungeons.hideBeforeTermsOnly = bool));
         hidePlayers.add(new ConfigBool(Text.literal("Hide players in range"), () -> Dungeons.hidePlayersInRange, bool -> Dungeons.hidePlayersInRange = bool));
-        hidePlayers.add(new ConfigDouble(Text.literal("Hiding range"), () -> Dungeons.hidePlayerRange, num -> Dungeons.hidePlayerRange = num, 0, 0, 7));
+        hidePlayers.add(new ConfigDouble(Text.literal("Hiding range"), () -> Dungeons.hidePlayerRange, num -> Dungeons.hidePlayerRange = num, 1, 0, 7));
 
         dungeons.add(hidePlayers);
 
diff --git a/src/main/java/blade/addon/utils/dungeon/DungeonClass.java b/src/main/java/blade/addon/utils/dungeon/DungeonClass.java
index 7783d71..1637fc1 100644
--- a/src/main/java/blade/addon/utils/dungeon/DungeonClass.java
+++ b/src/main/java/blade/addon/utils/dungeon/DungeonClass.java
@@ -8,6 +8,7 @@ import blade.addon.utils.events.Events;
 import net.fabricmc.fabric.api.client.message.v1.ClientReceiveMessageEvents;
 import net.minecraft.client.MinecraftClient;
 import net.minecraft.client.network.ClientPlayNetworkHandler;
+import net.minecraft.client.network.ClientPlayerEntity;
 import net.minecraft.client.network.PlayerListEntry;
 import net.minecraft.text.Text;
 
@@ -61,7 +62,8 @@ public enum DungeonClass {
     }
 
     private static void detectClasses() {
-        ClientPlayNetworkHandler networkHandler = MinecraftClient.getInstance().getNetworkHandler();
+        MinecraftClient client = MinecraftClient.getInstance();
+        ClientPlayNetworkHandler networkHandler = client.getNetworkHandler();
         if (networkHandler == null) return;
         Collection<PlayerListEntry> entryCollection = networkHandler.getPlayerList();
 
@@ -78,6 +80,15 @@ public enum DungeonClass {
                 nameClassMap.put(name, className);
             }
         }
+
+        //incase currentClass could not detect class before
+        if (currentClass == null) {
+            ClientPlayerEntity player = client.player;
+            if (player == null) return;
+            String name = player.getStringifiedName();
+            currentClass = nameClassMap.get(name);
+
+        }
     }
 
     public static DungeonClass getClass(String playerName) {
diff --git a/src/main/java/blade/addon/utils/events/Events.java b/src/main/java/blade/addon/utils/events/Events.java
index 38f6d4d..8cb96e1 100644
--- a/src/main/java/blade/addon/utils/events/Events.java
+++ b/src/main/java/blade/addon/utils/events/Events.java
@@ -1,5 +1,6 @@
 package blade.addon.utils.events;
 
+import blade.addon.utils.events.interfaces.BlockEntityEvent;
 import blade.addon.utils.events.interfaces.BlockInteractionEvent;
 import blade.addon.utils.events.interfaces.CancelableMessageEvent;
 import blade.addon.utils.events.interfaces.EntityEvent;
@@ -44,5 +45,7 @@ public class Events {
 
     public static final CancelableEventHandler<PlaySoundEvent> ON_SOUND = new CancelableEventHandler<>();
 
+    public static final EventHandler<BlockEntityEvent> ON_BLOCK_ENTITY = new EventHandler<>();
+
 
 }
diff --git a/src/main/java/blade/addon/utils/events/interfaces/BlockEntityEvent.java b/src/main/java/blade/addon/utils/events/interfaces/BlockEntityEvent.java
new file mode 100644
index 0000000..13483bc
--- /dev/null
+++ b/src/main/java/blade/addon/utils/events/interfaces/BlockEntityEvent.java
@@ -0,0 +1,9 @@
+package blade.addon.utils.events.interfaces;
+
+import net.minecraft.block.entity.BlockEntity;
+
+public interface BlockEntityEvent {
+
+    void on(BlockEntity blockEntity);
+
+}
diff --git a/src/main/resources/blade-addons.mixins.json b/src/main/resources/blade-addons.mixins.json
index 09181ea..56d8bbe 100644
--- a/src/main/resources/blade-addons.mixins.json
+++ b/src/main/resources/blade-addons.mixins.json
@@ -3,13 +3,14 @@
   "package": "blade.addon.mixin",
   "compatibilityLevel": "JAVA_21",
   "mixins": [
-    "ArrowMixin",
+    "ClientConnectionMixin",
+    "ClientPlayNetworkHandlerMixin",
+    "ChunkMixin",
     "EntityMixin",
-    "InventoryMixin",
     "ItemStackMixin",
-    "NetworkMixin",
     "PlayerEntityMixin",
-    "ServerTickMixin"
+    "PlayerInventoryMixin",
+    "StuckArrowsFeatureRendererMixin"
   ],
   "injectors": {
     "defaultRequire": 1
@@ -18,21 +19,18 @@
     "requireAnnotations": true
   },
   "client": [
-    "BlockEntityRenderMixin",
     "ClientPlayerEntityMixin",
+    "ClientPlayNetworkHandlerMixin",
     "EntityRendererMixin",
-    "FireMixin",
+    "EntityRenderManagerMixin",
     "HandledScreenMixin",
     "HeldItemRendererMixin",
     "InGameHudMixin",
     "Invento
…… diff 过长已截断 ……
- added selectable mobhighlight width - added distance to ledge on yellow pad only - added compact hoppity msg - added hide players in specific range - added disable drop animation in dungeons - added hide notification for yourself - added hide most titles in terminals phase - made so mimic toggle is separate from the global toggle - added disable recipe book - added inventory buttons (not finished) - added color option to chest count - disabled melody msg when its your own name - reworked positional messages and location notifier - added custom rag sound - added mask progress highlight
- hopefully fixed realistic SS setting missing some ticks - fixed practice ss saving times weirdly - fixed predev timer still tracking after p2 - fixed prec drone not having a sprite - fixed pillar explosion timer and storm crush notification being reversed - fixed terminal events only being processed if terminalsplits are on - fixed progress bar not being like valleys (added a toggle) - fixed pre4 notifier triggering on all devices - fixed item protection not always working in sell menus NOTE: sounds from at msg will be reset sadly with how im changing the way i save it but will make it easier to change sound
Gabriel Ellnebrand · 2025-12-30 · b2dcaad
查看变更
gradle.properties                                                                                    |   5 +-
 src/main/java/blade/addon/Bladeaddons.java                                                           |  22 +++++---
 src/main/java/blade/addon/features/dungeon/ChestCounter.java                                         |   2 +-
 src/main/java/blade/addon/features/dungeon/HidePlayers.java                                          |  20 ++++++--
 src/main/java/blade/addon/features/dungeon/f7/DistanceToLedge.java                                   |   9 ++++
 src/main/java/blade/addon/features/dungeon/f7/PillarExplode.java                                     |   8 +--
 src/main/java/blade/addon/features/dungeon/f7/PositionMessages.java                                  |  40 ---------------
 src/main/java/blade/addon/features/dungeon/f7/PredevTimer.java                                       |  14 ++---
 src/main/java/blade/addon/features/dungeon/f7/RelicTimer.java                                        |  26 +++++++---
 src/main/java/blade/addon/features/dungeon/f7/StormTickTimer.java                                    |   1 +
 src/main/java/blade/addon/features/dungeon/f7/{ => invincibility}/InvincibilityTimer.java            |  17 ++++--
 src/main/java/blade/addon/features/dungeon/f7/invincibility/MaskHighlight.java                       |  51 ++++++++++++++++++
 src/main/java/blade/addon/features/dungeon/f7/invincibility/MaskHolder.java                          |  12 +++++
 src/main/java/blade/addon/features/dungeon/f7/invincibility/MaskType.java                            |   5 ++
 src/main/java/blade/addon/features/dungeon/f7/{ => location}/LocationNotifier.java                   |  16 ++++--
 src/main/java/blade/addon/features/dungeon/f7/location/PositionMessage.java                          |  69 +++++++++++++++++++++++++
 src/main/java/blade/addon/features/dungeon/f7/location/PositionMessages.java                         | 127 +++++++++++++++++++++++++++++++++++++++++++++
 src/main/java/blade/addon/features/dungeon/f7/{ => terms}/GoldorTickTimer.java                       |   2 +-
 src/main/java/blade/addon/features/dungeon/f7/{ => terms}/LeapNotification.java                      |   2 +-
 src/main/java/blade/addon/features/dungeon/f7/{ => terms}/MelodyWarning.java                         |  16 +++++-
 src/main/java/blade/addon/features/dungeon/f7/{ => terms}/Pre4Notifier.java                          |   7 +--
 src/main/java/blade/addon/features/dungeon/f7/{ => terms}/TermStartTimer.java                        |   2 +-
 src/main/java/blade/addon/features/dungeon/f7/terms/TitleHider.java                                  |  13 +++++
 src/main/java/blade/addon/features/highlight/MobHighlight.java                                       |   5 +-
 src/main/java/blade/addon/features/item/DropAnimation.java                                           |  58 +++++++++++++++++++++
 src/main/java/blade/addon/features/item/ProtectItem.java                                             |   2 +-
 src/main/java/blade/addon/features/other/CompactHoppity.java                                         |  49 ++++++++++++++++++
 src/main/java/blade/addon/features/other/InventoryButton.java                                        |  64 +++++++++++++++++++++++
 src/main/java/blade/addon/features/other/PracticeSS.java                                             | 142 +++++++++++++++++++++++++++++++--------------------
 src/main/java/blade/addon/features/other/RagDisplay.java                                             |  19 +++++--
 src/main/java/blade/addon/mixin/BlockEntityRenderMixin.java                                          |   2 +-
 src/main/java/blade/addon/mixin/ClientPlayerEntityMixin.java                                         |  12 ++++-
 src/main/java/blade/addon/mixin/HandledScreenMixin.java                                              |   6 +++
 src/main/java/blade/addon/mixin/HeldItemRendererMixin.java                                           |  64 +++++++++++++++++++++++
 src/main/java/blade/addon/mixin/InGameHudMixin.java                                                  |  49 ++++++++++++++++--
 src/main/java/blade/addon/mixin/InventoryScreenMixin.java                                            |  59 +++++++++++++++++++++
 src/main/java/blade/addon/mixin/ItemStackMixin.java                                                  |  24 ++++++++-
 src/main/java/blade/addon/mixin/MinecraftClientMixin.java                                            |   7 +++
 src/main/java/blade/addon/mixin/NetworkMixin.java                                                    |  11 +---
 src/main/java/blade/addon/mixin/RecipeBookScreenMixin.java                                           |  18 +++++++
 src/main/java/blade/addon/mixin/RecipeBookWidgetMixin.java                                           |  36 +++++++++++++
 src/main/java/blade/addon/mixin/ServerTickMixin.java                                                 |   8 ++-
 src/main/java/blade/addon/utils/Commands.java                                                        |  23 ++++++++-
 src/main/java/blade/addon/utils/Debug.java                                                           |  17 +++++-
 src/main/java/blade/addon/utils/JsonUtility.java                                                     |  57 ---------------------
 src/main/java/blade/addon/utils/Misc.java                                                            |  41 ++++++++++++---
 src/main/java/blade/addon/utils/Scheduler.java                                                       |  11 ++++
 src/main/java/blade/addon/utils/config/Config.java                                                   |  44 ++++++++++++----
 src/main/java/blade/addon/utils/config/components/CombinedScreenNotifications.java                   |   4 +-
 src/main/java/blade/addon/utils/config/components/CombinedTickTimer.java                             |   4 +-
 src/main/java/blade/addon/utils/config/components/Components.java                                    |  14 ++---
 src/main/java/blade/addon/utils/config/values/Buttons.java                                           |  50 ++++++++++++++++++
 src/main/java/blade/addon/utils/config/values/Dungeons.java                                          |  17 +++++-
 src/main/java/blade/addon/utils/config/values/ExtraOptions.java                                      |  18 +++++++
 src/main/java/blade/addon/utils/config/values/Floor7.java                                            |  22 +++++---
 src/main/java/blade/addon/utils/dungeon/Phase.java                                                   |  11 ++--
 src/main/java/blade/addon/utils/dungeon/PositionMessage.java                                         |  46 -----------------
 src/main/java/blade/addon/utils/dungeon/Section.java                                                 |  52 ++++++++++++++++---
 src/main/java/blade/addon/utils/events/Events.java                                                   |  14 +++--
 src/main/java/blade/addon/utils/events/interfaces/CancelableMessageEvent.java                        |   8 +++
 src/main/java/blade/addon/utils/events/interfaces/PlaySoundEvent.java                                |   2 +-
 src/main/java/blade/addon/utils/interfaces/GameHud.java                                              |   7 +++
 src/main/java/blade/addon/utils/rendering/RenderLayers.java                                          |  27 ++++++++++
 src/main/java/blade/addon/utils/rendering/RenderPipelines.java                                       |   6 +++
 src/main/java/blade/addon/utils/times/PersonalBests.java                                             |   4 +-
 .../assets/blade-addons/textures/gui/sprites/pets/{flying-fish-mythic.png => flying-fish.png}        | Bin
 src/main/resources/assets/blade-addons/textures/gui/sprites/pets/precursor-drone.png                 | Bin 0 -> 2685 bytes
 src/main/resources/blade-addons.mixins.json                                                          |   4 ++
 src/main/resources/data/positions.json                                                               |  81 -----------------------------
 69 files changed, 1297 insertions(+), 408 deletions(-)
diff --git a/gradle.properties b/gradle.properties
index 157c1a8..5926cac 100644
--- a/gradle.properties
+++ b/gradle.properties
@@ -13,12 +13,13 @@ loader_version=0.17.3
 loom_version=1.13-SNAPSHOT
 
 # Mod Properties
-mod_version=2.2.5
+mod_version=2.2.6
 maven_group=blade.addon
 archives_base_name=blade-addons
 
 # Dependencies
 fabric_version=0.136.0+1.21.8
-practical_config=1.3.5
+# I love dependencies sometimes breaking
+practical_config=1.3.7
 modmenu_version = 15.0.0
 hypixel_api_version = 1.0.1
\ No newline at end of file
diff --git a/src/main/java/blade/addon/Bladeaddons.java b/src/main/java/blade/addon/Bladeaddons.java
index 3c4af01..2a78ffc 100644
--- a/src/main/java/blade/addon/Bladeaddons.java
+++ b/src/main/java/blade/addon/Bladeaddons.java
@@ -15,19 +15,21 @@ import blade.addon.features.dungeon.f7.BossWaypoints;
 import blade.addon.features.dungeon.f7.CrystalSpawn;
 import blade.addon.features.dungeon.f7.DistanceToLedge;
 import blade.addon.features.dungeon.f7.DragSpawnTimer;
-import blade.addon.features.dungeon.f7.GoldorTickTimer;
-import blade.addon.features.dungeon.f7.InvincibilityTimer;
-import blade.addon.features.dungeon.f7.LeapNotification;
-import blade.addon.features.dungeon.f7.LocationNotifier;
-import blade.addon.features.dungeon.f7.MelodyWarning;
+import blade.addon.features.dungeon.f7.terms.GoldorTickTimer;
+import blade.addon.features.dungeon.f7.invincibility.InvincibilityTimer;
+import blade.addon.features.dungeon.f7.terms.LeapNotification;
+import blade.addon.features.dungeon.f7.location.LocationNotifier;
+import blade.addon.features.dungeon.f7.terms.MelodyWarning;
 import blade.addon.features.dungeon.f7.PillarExplode;
-import blade.addon.features.dungeon.f7.PositionMessages;
-import blade.addon.features.dungeon.f7.Pre4Notifier;
+import blade.addon.features.dungeon.f7.location.PositionMessages;
+import blade.addon.features.dungeon.f7.terms.Pre4Notifier;
 import blade.addon.features.dungeon.f7.PredevTimer;
 import blade.addon.features.dungeon.f7.RelicTimer;
 import blade.addon.features.dungeon.f7.StormTickTimer;
-import blade.addon.features.dungeon.f7.TermStartTimer;
+import blade.addon.features.dungeon.f7.terms.TermStartTimer;
 import blade.addon.features.highlight.MobHighlight;
+import blade.addon.features.item.DropAnimation;
+import blade.addon.features.other.CompactHoppity;
 import blade.addon.features.other.DianaNotifier;
 import blade.addon.features.other.KickedTimer;
 import blade.addon.features.other.PracticeSS;
@@ -41,6 +43,7 @@ import blade.addon.utils.Scheduler;
 import blade.addon.utils.config.Config;
 import blade.addon.utils.config.FolderUtility;
 import blade.addon.utils.config.components.Components;
+import blade.addon.utils.config.values.Buttons;
 import blade.addon.utils.dungeon.DungeonClass;
 import blade.addon.utils.dungeon.Phase;
 import blade.addon.utils.dungeon.Section;
@@ -52,6 +55,7 @@ public class Bladeaddons implements ModInitializer {
 	public void onInitialize() {
         FolderUtility.init();
         Components.init();
+        Buttons.init();
         Config.manager.load();
 		Keybinds.init();
         CustomEvents.init();
@@ -95,5 +99,7 @@ public class Bladeaddons implements ModInitializer {
         RagDisplay.init();
         LeapNotification.init();
         PracticeSS.init();
+        CompactHoppity.init();
+        DropAnimation.init();
     }
 }
\ No newline at end of file
diff --git a/src/main/java/blade/addon/features/dungeon/ChestCounter.java b/src/main/java/blade/addon/features/dungeon/ChestCounter.java
index ab1beb3..d9b4828 100644
--- a/src/main/java/blade/addon/features/dungeon/ChestCounter.java
+++ b/src/main/java/blade/addon/features/dungeon/ChestCounter.java
@@ -68,7 +68,7 @@ public class ChestCounter {
         int y = component.getScaledY();
 
         if (hasUpdatedData) {
-            context.drawText(MinecraftClient.getInstance().textRenderer, Text.literal("Chests: " + Math.min(chestDisplayCount + countedChests, 60)), x, y, 0xffffffff, true);
+            context.drawText(MinecraftClient.getInstance().textRenderer, Text.literal("Chests: " + Math.min(chestDisplayCount + countedChests, 60)), x, y, Dungeons.chestCountColor , true);
         } else {
             context.drawText(MinecraftClient.getInstance().textRenderer, Text.literal("go to the dungeon hub"), x, y, 0xffff0000, true);
 
diff --git a/src/main/java/blade/addon/features/dungeon/HidePlayers.java b/src/main/java/blade/addon/features/dungeon/HidePlayers.java
index 8c1cc45..9e4efb6 100644
--- a/src/main/java/blade/addon/features/dungeon/HidePlayers.java
+++ b/src/main/java/blade/addon/features/dungeon/HidePlayers.java
@@ -1,9 +1,13 @@
 package blade.addon.features.dungeon;
 
 import blade.addon.utils.Location;
+import blade.addon.utils.Misc;
 import blade.addon.utils.config.values.Dungeons;
+import blade.addon.utils.detection.Detection;
 import blade.addon.utils.dungeon.Phase;
 import blade.addon.utils.events.Events;
+import net.minecraft.client.MinecraftClient;
+import net.minecraft.client.network.ClientPlayerEntity;
 import net.minecraft.entity.player.PlayerEntity;
 import net.minecraft.util.math.Vec3d;
 
@@ -47,14 +51,24 @@ public class HidePlayers {
     public static boolean shouldHidePlayers(PlayerEntity player) {
         if (!Location.inDungeon()) return false;
 
-        //in case it's a mob instead of a player
-        if (player.getName().getString().contains(" ")) return false;
+        if (!Detection.isARealPlayer(player)) return false;
+        if (Misc.isClientPlayer(player)) return false;
 
         if (testHideAtLeap()) {
             return true;
         }
 
-        return testHideAtSS(player);
+        if (testHideAtSS(player)) {
+            return true;
+        }
+
+        if (Dungeons.hidePlayersInRange) {
+            ClientPlayerEntity clientPlayer = MinecraftClient.getInstance().player;
+            if (clientPlayer == null) return false;
+            return player.getPos().distanceTo(clientPlayer.getPos()) <= Dungeons.hidePlayerRange;
+        }
+
+        return false;
     }
 
 
diff --git a/src/main/java/blade/addon/features/dungeon/f7/DistanceToLedge.java b/src/main/java/blade/addon/features/dungeon/f7/DistanceToLedge.java
index ff68309..f3d9605 100644
--- a/src/main/java/blade/addon/features/dungeon/f7/DistanceToLedge.java
+++ b/src/main/java/blade/addon/features/dungeon/f7/DistanceToLedge.java
@@ -11,9 +11,11 @@ import net.minecraft.client.MinecraftClient;
 import net.minecraft.client.gui.DrawContext;
 import net.minecraft.client.network.ClientPlayerEntity;
 import net.minecraft.text.Text;
+import net.minecraft.util.math.Box;
 
 public class DistanceToLedge {
 
+    private static final Box YELLOW_PAD = new Box(20, 163, 0, 58, 213, 107);
     private static final double MIN_X = 33.704;
 
     public static void init() {
@@ -27,8 +29,15 @@ public class DistanceToLedge {
         return player.getX() - MIN_X;
     }
 
+    private static boolean isAtYellow() {
+        ClientPlayerEntity player = MinecraftClient.getInstance().player;
+        if (player == null) return false;
+        return YELLOW_PAD.contains(player.getPos());
+    }
+
     public static boolean display() {
         if (!Floor7.displayDistanceToLedge || !Location.inDungeon()) return false;
+        if (Floor7.showDistanceAtYellowOnly && !isAtYellow()) return false;
         return Phase.inP2() && !Phase.stormDead() && (DungeonClass.isClass(DungeonClass.MAGE) || DungeonClass.isClass(DungeonClass.ARCHER));
     }
 
diff --git a/src/main/java/blade/addon/features/dungeon/f7/PillarExplode.java b/src/main/java/blade/addon/features/dungeon/f7/PillarExplode.java
index f42aea9..fad83ec 100644
--- a/src/main/java/blade/addon/features/dungeon/f7/PillarExplode.java
+++ b/src/main/java/blade/addon/features/dungeon/f7/PillarExplode.java
@@ -27,7 +27,7 @@ public class PillarExplode {
             String string = text.getString();
             if (string == null) return;
 
-            if (string.equals("[BOSS] Storm: Oof") ||string.equals("[BOSS] Storm: Ouch, that hurt!")) {
+            if (string.equals("[BOSS] Storm: Oof") || string.equals("[BOSS] Storm: Ouch, that hurt!")) {
                 tick = TOTAL_TICKS;
                 Scheduler.scheduleSound(SoundEvents.BLOCK_NOTE_BLOCK_PLING.value(), 1, 1);
             }
@@ -37,14 +37,14 @@ public class PillarExplode {
     }
 
     public static boolean displayTimer() {
-        return Floor7.notifyStormCrush && tick > 0;
+        return Floor7.timePillarExplosion && tick > 0;
     }
 
     public static void renderTimer(HUDComponent component, DrawContext context) {
         int x = component.getScaledX();
         int y = component.getScaledY();
 
-        Formatting formatting = (tick < 6? Formatting.GREEN: Formatting.RED);
+        Formatting formatting = (tick < 6 ? Formatting.GREEN : Formatting.RED);
 
         double num = tick * Constants.TICK_DURATION;
 
@@ -53,7 +53,7 @@ public class PillarExplode {
     }
 
     public static boolean display() {
-        return Floor7.timePillarExplosion && tick > 0;
+        return Floor7.notifyStormCrush && tick > 0;
     }
 
     public static void render(HUDComponent component, DrawContext context) {
diff --git a/src/main/java/blade/addon/features/dungeon/f7/PositionMessages.java b/src/main/java/blade/addon/features/dungeon/f7/PositionMessages.java
deleted file mode 100644
index f336854..0000000
--- a/src/main/java/blade/addon/features/dungeon/f7/PositionMessages.java
+++ /dev/null
@@ -1,40 +0,0 @@
-package blade.addon.features.dungeon.f7;
-
-import blade.addon.utils.JsonUtility;
-import blade.addon.utils.Location;
-import blade.addon.utils.config.values.Floor7;
-import blade.addon.utils.dungeon.Phase;
-import blade.addon.utils.dungeon.PositionMessage;
-import blade.addon.utils.events.Events;
-import net.fabricmc.fabric.api.client.event.lifecycle.v1.ClientTickEvents;
-import net.minecraft.client.MinecraftClient;
-import net.minecraft.client.network.ClientPlayerEntity;
-
-import java.util.ArrayList;
-
-public class PositionMessages {
-
-    private static final ArrayList<PositionMessage> positionMessages = JsonUtility.readPositionalMessages("/data/positions.json");
-
-    public static void init() {
-        Events.ON_LOCATION_CHANGE.register(newLocation -> {
-            if (Location.inDungeon()) {
-                for (PositionMessage message : positionMessages) {
-                    message.reset();
-                }
-            }
-        });
-
-        ClientTickEvents.END_CLIENT_TICK.register(PositionMessages::tick);
-    }
-
-    public static void tick(MinecraftClient client) {
-        if (!Location.inDungeon() || !Phase.inP3() || !Floor7.enablePositionalMessages) return;
-        ClientPlayerEntity player = client.player;
-        if (player == null) return;
-
-        for (PositionMessage message : positionMessages) {
-            message.tick(player);
-        }
-    }
-}
diff --git a/src/main/java/blade/addon/features/dungeon/f7/PredevTimer.java b/src/main/java/blade/addon/features/dungeon/f7/PredevTimer.java
index a507c97..bf97020 100644
--- a/src/main/java/blade/addon/features/dungeon/f7/PredevTimer.java
+++ b/src/main/java/blade/addon/features/dungeon/f7/PredevTimer.java
@@ -14,7 +14,7 @@ public class PredevTimer {
 
     private static long bossEnterTime = 0;
     private static boolean at3rdDev = false;
-    private static boolean enteredBoss = false;
+    private static boolean shouldTrack = false;
 
     public static void init() {
 
@@ -22,14 +22,16 @@ public class PredevTimer {
             if (Phase.inP1()) {
                 bossEnterTime = System.currentTimeMillis();
                 at3rdDev = false;
-                enteredBoss = true;
+                shouldTrack = true;
+            } else if (Phase.inP3()) {
+                shouldTrack = false;
             }
         });
 
-        Events.ON_LOCATION_CHANGE.register(newLocation -> enteredBoss = false);
+        Events.ON_LOCATION_CHANGE.register(newLocation -> shouldTrack = false);
 
         ClientTickEvents.END_CLIENT_TICK.register(client -> {
-            if (!enteredBoss || at3rdDev || (!Floor7.predevForAll && !DungeonClass.isClass(DungeonClass.HEALER))) return;
+            if (!shouldTrack || at3rdDev || (!Floor7.predevForAll && !DungeonClass.isClass(DungeonClass.HEALER))) return;
             ClientPlayerEntity player = client.player;
             if (player == null) return;
             if (Misc.getDistance(player.getX(), player.getZ(), 1, 77) <= 3) {
@@ -38,10 +40,10 @@ public class PredevTimer {
         });
 
         Events.ON_LEAP.register(message -> {
-            if (at3rdDev && enteredBoss && (DungeonClass.isClass(DungeonClass.HEALER) || Floor7.predevForAll)) {
+            if (at3rdDev && shouldTrack && (DungeonClass.isClass(DungeonClass.HEALER) || Floor7.predevForAll)) {
                 PersonalBests.predevTime.testNewTime(Text.literal("§aPredev completed in "), bossEnterTime);
                 at3rdDev = false;
-                enteredBoss = false;
+                shouldTrack = false;
             }
         });
 
diff --git a/src/main/java/blade/addon/features/dungeon/f7/RelicTimer.java b/src/main/java/blade/addon/features/dungeon/f7/RelicTimer.java
index 394be5f..c1a01a8 100644
--- a/src/main/java/blade/addon/features/dungeon/f7/RelicTimer.java
+++ b/src/main/java/blade/addon/features/dungeon/f7/RelicTimer.java
@@ -240,16 +240,28 @@ public class RelicTimer {
         int y = component.getScaledY();
 
         //from valleyAddons
+
         StringBuilder message = new StringBuilder("§8[");
 
-        int diff = Floor7.relicSpawnTicks - tick;
-        for (int i = 0; i < Floor7.relicSpawnTicks; i++) {
-            if (i < diff) {
-                if (tick < 2) message.append("§a|");
-                else message.append("§c|");
-            } else message.append("§7|");
+        if (Floor7.useValleyBar) {
+            for (int i = 0; i < Floor7.relicSpawnTicks; ++i) {
+                if (i < tick) {
+                    if (tick > 2) message.append("§a|");
+                    else message.append("§c|");
+                }
+                else message.append("§7|");
+            }
+            message.append("§8]");
+        } else {
+            int diff = Floor7.relicSpawnTicks - tick;
+            for (int i = 0; i < Floor7.relicSpawnTicks; i++) {
+                if (i < diff) {
+                    if (tick < 2) message.append("§a|");
+                    else message.append("§c|");
+                } else message.append("§7|");
+            }
+            message.append("§8]");
         }
-        message.append("§8]");
 
         RenderUtils.drawCenteredText(context, MinecraftClient.getInstance().textRenderer, Text.literal(message.toString()), x, y, component.getWidth(), 0xffffffff);
 
diff --git a/src/main/java/blade/addon/features/dungeon/f7/StormTickTimer.java b/src/main/java/blade/addon/features/dungeon/f7/StormTickTimer.java
index c1f5911..86c35d8 100644
--- a/src/main/java/blade/addon/features/dungeon/f7/StormTickTimer.java
+++ b/src/main/java/blade/addon/features/dungeon/f7/StormTickTimer.java
@@ -1,5 +1,6 @@
 package blade.addon.features.dungeon.f7;
 
+import blade.addon.features.dungeon.f7.invincibility.InvincibilityTimer;
 import blade.addon.utils.Constants;
 import blade.addon.utils.Location;
 import blade.addon.utils.Misc;
diff --git a/src/main/java/blade/addon/features/dungeon/f7/InvincibilityTimer.java b/src/main/java/blade/addon/features/dungeon/f7/invincibility/InvincibilityTimer.java
similarity index 93%
rename from src/main/java/blade/addon/features/dungeon/f7/InvincibilityTimer.java
rename to src/main/java/blade/addon/features/dungeon/f7/invincibility/InvincibilityTimer.java
index ea67937..b506325 100644
--- a/src/main/java/blade/addon/features/dungeon/f7/InvincibilityTimer.java
+++ b/src/main/java/blade/addon/features/dungeon/f7/invincibility/InvincibilityTimer.java
@@ -1,5 +1,6 @@
-package blade.addon.features.dungeon.f7;
+package blade.addon.features.dungeon.f7.invincibility;
 
+import blade.addon.features.dungeon.f7.terms.Pre4Notifier;
 import blade.addon.utils.Constants;
 import blade.addon.utils.Location;
 import blade.addon.utils.Misc;
@@ -114,19 +115,19 @@ public class InvincibilityTimer {
         Matcher matcher = BONZO_PATTERN.matcher(string);
         if (matcher.matches()) {
             bonzoMaskTicks = BONZO_MASK_COOLDOWN;
-            if (Dungeons.showProcTitle) {
+            if (Dungeons.showProcTitle && !Pre4Notifier.atDev()) {
                 Misc.setTitle(Text.literal("Bonzo"));
             }
         }
         if (string.equals("Second Wind Activated! Your Spirit Mask saved your life!")) {
             spiritMaskTicks = SPIRIT_MASK_COOLDOWN;
-            if (Dungeons.showProcTitle) {
+            if (Dungeons.showProcTitle && !Pre4Notifier.atDev()) {
                 Misc.setTitle(Text.literal("Spirit"));
             }
         }
         if (string.equals("Your Phoenix Pet saved you from certain death!")) {
             phoenixTicks = PHOENIX_COOLDOWN;
-            if (Dungeons.showProcTitle) {
+            if (Dungeons.showProcTitle && !Pre4Notifier.atDev()) {
                 Misc.setTitle(Text.literal("Phoenix"));
             }
         }
@@ -167,6 +168,14 @@ public class InvincibilityTimer {
         }
     }
 
+    public static double getBonzoProgress() {
+        return (double) bonzoMaskTicks / BONZO_MASK_COOLDOWN;
+    }
+
+    public static double getSpiritProgress() {
+        return (double) spiritMaskTicks / SPIRIT_MASK_COOLDOWN;
+    }
+
     private static void drawSprite(DrawContext context, Identifier identifier, int x, int y, boolean isOn, int ticks, Text timerText) {
 
         int color;
diff --git a/src/main/java/blade/addon/features/dungeon/f7/invincibility/MaskHighlight.java b/src/main/java/blade/addon/features/dungeon/f7/invincibility/MaskHighlight.java
new file mode 100644
index 0000000..3c36558
--- /dev/null
+++ b/src/main/java/blade/addon/features/dungeon/f7/invincibility/MaskHighlight.java
@@ -0,0 +1,51 @@
+package blade.addon.features.dungeon.f7.invincibility;
+
+import net.minecraft.client.gui.DrawContext;
+import net.minecraft.component.DataComponentTypes;
+import net.minecraft.component.type.NbtComponent;
+import net.minecraft.item.ItemStack;
+import net.minecraft.nbt.NbtCompound;
+
+public class MaskHighlight {
+
+    private static final int COLOR = 0xbb666666;
+
+    public static void draw(DrawContext context, ItemStack stack, int x, int y) {
+        MaskHolder holder = (MaskHolder) (Object) stack;
+        assert holder != null;
+
+        if (!holder.blade_addons$scannedMask()) {
+            MaskType mask = getMaskType(stack);
+            holder.blade_addons$setMask(mask);
+        }
+
+        MaskType mask = holder.blade_addons$getMask();
+
+        int progress = 0;
+        switch (mask) {
+            case SPIRIT -> progress = (int) (InvincibilityTimer.getSpiritProgress() * 16);
+            case BONZO -> progress = (int) (InvincibilityTimer.getBonzoProgress() * 16);
+        }
+        if (progress == 0) return;
+        context.fill(x, y + 16 - progress, x + 16, y + 16, COLOR);
+    }
+
+    public static MaskType getMaskType(ItemStack item) {
+        String id = getId(item);
+        if (id == null) return MaskType.NONE;
+
+        if (id.contains("SPIRIT_MASK")) return MaskType.SPIRIT;
+        if (id.contains("BONZO_MASK")) return MaskType.BONZO;
+        return MaskType.NONE;
+    }
+
+    public static String getId(ItemStack item) {
+        NbtComponent nbt = item.get(DataComponentTypes.CUSTOM_DATA);
+        if (nbt == null) return null;
+
+        NbtCompound compound = nbt.copyNbt();
+
+        return compound.getString("id", null);
+    }
+
+}
diff --git a/src/main/java/blade/addon/features/dungeon/f7/invincibility/MaskHolder.java b/src/main/java/blade/addon/features/dungeon/f7/invincibility/MaskHolder.java
new file mode 100644
index 0000000..0b669f3
--- /dev/null
+++ b/src/main/java/blade/addon/features/dungeon/f7/invincibility/MaskHolder.java
@@ -0,0 +1,12 @@
+package blade.addon.features.dungeon.f7.invincibility;
+
+public interface MaskHolder {
+
+    MaskType maskType = null;
+
+    boolean blade_addons$scannedMask();
+
+    void blade_addons$setMask(MaskType maskType);
+
+    MaskType blade_addons$getMask();
+}
diff --git a/src/main/java/blade/addon/features/dungeon/f7/invincibility/MaskType.java b/src/main/java/blade/addon/features/dungeon/f7/invincibility/MaskType.java
new file mode 100644
index 0000000..6255760
--- /dev/null
+++ b/src/main/java/blade/addon/features/dungeon/f7/invincibility/MaskType.java
@@ -0,0 +1,5 @@
+package blade.addon.features.dungeon.f7.invincibility;
+
+public enum MaskType {
+    BONZO, SPIRIT, NONE;
+}
diff --git a/src/main/java/blade/addon/features/dungeon/f7/LocationNotifier.java b/src/main/java/blade/addon/features/dungeon/f7/location/LocationNotifier.java
similarity index 79%
rename from src/main/java/blade/addon/features/dungeon/f7/LocationNotifier.java
rename to src/main/java/blade/addon/features/dungeon/f7/location/LocationNotifier.java
index 5187488..34aaf2b 100644
--- a/src/main/java/blade/addon/features/dungeon/f7/LocationNotifier.java
+++ b/src/main/java/blade/addon/features/dungeon/f7/location/LocationNotifier.java
@@ -1,4 +1,4 @@
-package blade.addon.features.dungeon.f7;
+package blade.addon.features.dungeon.f7.location;
 
 import blade.addon.utils.Location;
 import blade.addon.utils.Scheduler;
@@ -9,10 +9,9 @@ import config.practical.hud.HUDComponent;
 import net.fabricmc.fabric.api.client.event.lifecycle.v1.ClientTickEvents;
 import net.minecraft.client.MinecraftClient;
 import net.minecraft.client.gui.DrawContext;
-import net.minecraft.sound.SoundEvent;
+import net.minecraft.client.network.ClientPlayerEntity;
 import net.minecraft.text.Text;
 import net.minecraft.util.Formatting;
-import net.minecraft.util.Identifier;
 
 import java.util.regex.Matcher;
 import java.util.regex.Pattern;
@@ -31,8 +30,16 @@ public class LocationNotifier {
             if (!matcher.find()) return;
             String action = matcher.group(1);
 
+            ClientPlayerEntity player = MinecraftClient.getInstance().player;
+            if (player == null) return;
+            Text name = player.getName();
+            if (name == null) return;
+            if (Floor7.dontNotifiyForYourself && name.getString().equals(username)) return;
             String screenNotification = (username + " is " + action + message.substring(action.length()) + "!").replaceAll("§.", "");
+            if (PositionMessages.hasBeenSent(screenNotification)) return;
             startNotification(screenNotification);
+
+
         });
 
         ClientTickEvents.END_CLIENT_TICK.register(client -> ticks = Math.max(ticks - 1, 0));
@@ -43,8 +50,7 @@ public class LocationNotifier {
         ticks = Floor7.notificationDuration;
 
         for (int i = 0; i < Floor7.notificationRepetitions; i++) {
-            SoundEvent event = SoundEvent.of(Identifier.of(Floor7.notificationSound));
-            Scheduler.scheduleSound(event, Floor7.notificationVolume, Floor7.notificationPitch, i + 1);
+            Scheduler.scheduleSound(Floor7.atLocationSound, i + 1);
         }
     }
 
diff --git a/src/main/java/blade/addon/features/dungeon/f7/location/PositionMessage.java b/src/main/java/blade/addon/features/dungeon/f7/location/PositionMessage.java
new file mode 100644
index 0000000..763d43a
--- /dev/null
+++ b/src/main/java/blade/addon/features/dungeon/f7/location/PositionMessage.java
@@ -0,0 +1,69 @@
+package blade.addon.features.dungeon.f7.location;
+
+import blade.addon.utils.config.values.Floor7;
+import blade.addon.utils.dungeon.Section;
+import net.minecraft.client.network.ClientPlayerEntity;
+import net.minecraft.util.math.Box;
+import net.minecraft.util.math.Vec3d;
+
+public class PositionMessage {
+
+    private final String message, check;
+    private final Box box;
+    private boolean sent;
+    private final int[] sections;
+
+    public PositionMessage(String message, String check, Box box, int[] sections) {
+        this.message = message;
+        this.check = check;
+        this.box = box;
+        this.sent = true;
+        this.sections = sections;
+        PositionMessages.positionMessages.add(this);
+    }
+
+    private boolean inRange(Vec3d pos) {
+        return box.contains(pos);
+    }
+
+    private boolean inAValidSection() {
+        for (int section : sections) {
+            if (Section.inSection(section)) return true;
+        }
+        return false;
+    }
+
+    public void tick(ClientPlayerEntity player) {
+        if (sent) return;
+        if (!inAValidSection()) return;
+        if (inRange(player.getPos())) {
+            sent = true;
+            if (Floor7.enablePositionalMessages) {
+                player.networkHandler.sendChatCommand("pc " + message);
+            }
+        }
+    }
+
+    public boolean hasBeenSent(String string) {
+        if (!string.toLowerCase().contains(check)) return false;
+
+        if (sent) {
+            return true;
+        } else {
+            sent = true;
+            return false;
+        }
+    }
+
+    public void setSent(boolean sent) {
+        this.sent = sent;
+    }
+
+    public boolean sent() {
+        return sent;
+    }
+
+    public Box getBox() {
+        return box;
+    }
+}
diff --git a/src/main/java/blade/addon/features/dungeon/f7/location/PositionMessages.java b/src/main/java/blade/addon/features/dungeon/f7/location/PositionMessages.java
new file mode 100644
index 0000000..7b7231f
--- /dev/null
+++ b/src/main/java/blade/addon/features/dungeon/f7/location/PositionMessages.java
@@ -0,0 +1,127 @@
+package blade.addon.features.dungeon.f7.location;
+
+import blade.addon.utils.Debug;
+import blade.addon.utils.Location;
+import blade.addon.utils.config.values.Floor7;
+import blade.addon.utils.dungeon.Phase;
+import blade.addon.utils.events.Events;
+import blade.addon.utils.rendering.RenderLayers;
+import net.fabricmc.fabric.api.client.event.lifecycle.v1.ClientTickEvents;
+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.client.network.ClientPlayerEntity;
+import net.minecraft.client.render.Camera;
+import net.minecraft.client.render.VertexConsumer;
+import net.minecraft.client.render.VertexConsumerProvider;
+import net.minecraft.client.render.VertexRendering;
+import net.minecraft.client.util.math.MatrixStack;
+import net.minecraft.util.math.Box;
+import net.minecraft.util.math.Vec3d;
+
+import java.util.ArrayList;
+
+public class PositionMessages {
+
+    static final ArrayList<PositionMessage> positionMessages = new ArrayList<>();
+
+    static PositionMessage SS = new PositionMessage("At SS!", "ss", new Box(107, 120, 93, 110, 121, 95), new int[]{0, 1});
+    static PositionMessage EE2 = new PositionMessage("At EE2!", "ee2", new Box(57, 109, 130, 59, 110, 132), new int[]{1, 2});
+    static PositionMessage HEE2 = new PositionMessage("At High EE2!", "ee2", new Box(59, 132, 138, 62, 133, 140), new int[]{1, 2});
+    static PositionMessage EE3 = new PositionMessage("At EE3!", "ee3", new Box(1, 109, 103, 3, 110, 106), new int[]{2, 3});
+    static PositionMessage CORE = new PositionMessage("At Core!", "core", new Box(53, 115, 51, 56, 116, 54), new int[]{2, 3, 4});
+    static PositionMessage TUNNEL = new PositionMessage("Inside Goldor Tunnel!", "tunnel", new Box(52, 114, 55, 57, 116, 58), new int[]{4, 5});
+    static PositionMessage SAFE_2 = new PositionMessage("At 2 Safespot!", "2 safespot", new Box(46, 109, 121.987, 49, 110, 121.988), new int[]{1, 2});
+    static PositionMessage SAFE_3 = new PositionMessage("At 3 Safespot!", "3 safespot", new Box(18, 121, 91, 19, 126, 99), new int[]{2, 3});
+    static PositionMessage SPLIT_2 = new PositionMessage("At Split ee2!", "split ee2", new Box(58, 119, 124, 60, 123, 126), new int[]{1, 2});
+    static PositionMessage SAFE_2_HIGH = new PositionMessage("At High 2 Safespot!", "2 safespot", new Box(70, 127, 143.7, 57, 133, 146.7), new int[]{1, 2});
+
+    public static void init() {
+        ClientTickEvents.END_CLIENT_TICK.register(PositionMessages::tick);
+        WorldRenderEvents.BEFORE_ENTITIES.register(PositionMessages::render);
+        Events.ON_LOCATION_CHANGE.register(newLocation -> {
+            disableAll();
+        });
+
+        Events.ON_PHASE_CHANGE.register(() -> {
+            if (Phase.inP2()) {
+                SS.setSent(false);
+            } else if (Phase.inTerminals()) {
+                EE2.setSent(false);
+                HEE2.setSent(false);
+                EE3.setSent(false);
+                CORE.setSent(false);
+                TUNNEL.setSent(false);
+                SAFE_2.setSent(false);
+                SAFE_3.setSent(false);
+                SPLIT_2.setSent(false);
+                SAFE_2_HIGH.setSent(false);
+            } else if (Phase.inGoldorTunnel()) {
+                EE2.setSent(true);
+                HEE2.setSent(true);
+                EE3.setSent(true);
+                CORE.setSent(true);
+                TUNNEL.setSent(true);
+                SAFE_2.setSent(true);
+                SAFE_3.setSent(true);
+                SPLIT_2.setSent(true);
+                SAFE_2_HIGH.setSent(true);
+            }
+        });
+
+
+    }
+
+    public static void tick(MinecraftClient client) {
+        if (!Location.inDungeon() || !Floor7.enablePositionalMessages) return;
+        ClientPlayerEntity player = client.player;
+        if (player == null) return;
+
+        for (PositionMessage message : positionMessages) {
+            message.tick(player);
+        }
+    }
+
+    public static boolean hasBeenSent(String message) {
+        for (PositionMessage positionMessage : positionMessages) {
+            if (positionMessage.hasBeenSent(message)) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    public static void disableAll() {
+        for (PositionMessage positionMessage : positionMessages) {
+            positionMessage.setSent(true);
+        }
+    }
+
+    private static void render(WorldRenderContext worldRenderContext) {
+        if (!Location.inDungeon() || !Debug.renderPositions) return;
+
+        Camera camera = worldRenderContext.camera();
+        Vec3d cameraPos = camera.getPos();
+        MatrixStack matrixStack = worldRenderContext.matrixStack();
+        if (matrixStack == null) return;
+        matrixStack.push();
+        matrixStack.translate(-cameraPos.x, -cameraPos.y, -cameraPos.z);
+
+        VertexConsumerProvider consumers = worldRenderContext.consumers();
+        if (consumers == null) return;
+        VertexConsumer consumer = consumers.getBuffer(RenderLayers.OUTLINE_LAYER);
+        for (PositionMessage positionMessage : positionMessages) {
+            Box box = positionMessage.getBox();
+
+            if (positionMessage.sent()) {
+                VertexRendering.drawBox(matrixStack, consumer, box, 0, 1, 0, 1);
+            } else {
+                VertexRendering.drawBox(matrixStack, consumer, box, 1, 0, 0, 1);
+            }
+        }
+
+
+        matrixStack.pop();
+
+    }
+}
diff --git a/src/main/java/blade/addon/features/dungeon/f7/GoldorTickTimer.java b/src/main/java/blade/addon/features/dungeon/f7/terms/GoldorTickTimer.java
similarity index 97%
rename from src/main/java/blade/addon/features/dungeon/f7/GoldorTickTimer.java
rename to src/main/java/blade/addon/features/dungeon/f7/terms/GoldorTickTimer.java
index c419fe5..afb214d 100644
--- a/src/main/java/blade/addon/features/dungeon/f7/GoldorTickTimer.java
+++ b/src/main/java/blade/addon/features/dungeon/f7/terms/GoldorTickTimer.java
@@ -1,4 +1,4 @@
-package blade.addon.features.dungeon.f7;
+package blade.addon.features.dungeon.f7.terms;
 
 import blade.addon.utils.Constants;
 import blade.addon.utils.Location;
diff --git a/src/main/java/blade/addon/features/dungeon/f7/LeapNotification.java b/src/main/java/blade/addon/features/dungeon/f7/terms/LeapNotification.java
similarity index 98%
rename from src/main/java/blade/addon/features/dungeon/f7/LeapNotification.java
rename to src/main/java/blade/addon/features/dungeon/f7/terms/LeapNotification.java
index 26a07da..bb08fb9 100644
--- a/src/main/java/blade/addon/features/dungeon/f7/LeapNotification.java
+++ b/src/main/java/blade/addon/features/dungeon/f7/terms/LeapNotification.java
@@ -1,4 +1,4 @@
-package blade.addon.features.dungeon.f7;
+package blade.addon.features.dungeon.f7.terms;
 
 import blade.addon.utils.Location;
 import blade.addon.utils.config.values.Floor7;
diff --git a/src/main/java/blade/addon/features/dungeon/f7/MelodyWarning.java b/src/main/java/blade/addon/features/dungeon/f7/terms/MelodyWarning.java
similarity index 81%
rename from src/main/java/blade/addon/features/dungeon/f7/MelodyWarning.java
rename to src/main/java/blade/addon/features/dungeon/f7/terms/MelodyWarning.java
index 72f57ee..03db5c9 100644
--- a/src/main/java/blade/addon/features/dungeon/f7/MelodyWarning.java
+++ b/src/main/java/blade/addon/features/dungeon/f7/terms/MelodyWarning.java
@@ -1,4 +1,4 @@
-package blade.addon.features.dungeon.f7;
+package blade.addon.features.dungeon.f7.terms;
 
 import blade.addon.utils.Location;
 import blade.addon.utils.config.values.Floor7;
@@ -9,6 +9,7 @@ import blade.addon.utils.rendering.RenderUtils;
 import config.practical.hud.HUDComponent;
 import net.minecraft.client.MinecraftClient;
 import net.minecraft.client.gui.DrawContext;
+import net.minecraft.client.network.ClientPlayerEntity;
 import net.minecraft.text.Text;
 
 import java.util.concurrent.CopyOnWriteArrayList;
@@ -22,6 +23,7 @@ public class MelodyWarning {
     private static final CopyOnWriteArrayList<String> names = new CopyOnWriteArrayList<>();
 
     private static boolean melodyStarted = false;
+    private static boolean ownUsername = false;
     private static String name;
     private static int furthestProgress = 0;
 
@@ -37,6 +39,7 @@ public class MelodyWarning {
                     melodyStarted = true;
                     name = username;
                     furthestProgress = progress;
+                    ownUsername = isOwnUsername(username);
                     if (!names.contains(username)) {
                         names.add(username);
                     }
@@ -59,15 +62,24 @@ public class MelodyWarning {
         });
     }
 
+    private static boolean isOwnUsername(String username) {
+        ClientPlayerEntity player = MinecraftClient.getInstance().player;
+        if (player == null) return false;
+        Text textName = player.getName();
+        if (textName == null) return false;
+        return textName.getString().equals(username);
+    }
+
     private static void reset() {
         names.clear();
         furthestProgress = 0;
         name = "";
         melodyStarted = false;
+        ownUsername = false;
     }
 
     public static boolean display() {
-        return melodyStarted && Floor7.notifiyMelody;
+        return melodyStarted && Floor7.notifiyMelody && !ownUsername;
     }
 
     public static void render(HUDComponent component, DrawContext context) {
diff --git a/src/main/java/blade/addon/features/dungeon/f7/Pre4Notifier.java b/src/main/java/blade/addon/features/dungeon/f7/terms/Pre4Notifier.java
similarity index 90%
rename from src/main/java/blade/addon/features/dungeon/f7/Pre4Notifier.java
rename to src/main/java/blade/addon/features/dungeon/f7/terms/Pre4Notifier.java
index b7bfe89..722ca34 100644
--- a/src/main/java/blade/addon/features/dungeon/f7/Pre4Notifier.java
+++ b/src/main/java/blade/addon/features/dungeon/f7/terms/Pre4Notifier.java
@@ -1,5 +1,6 @@
-package blade.addon.features.dungeon.f7;
+package blade.addon.features.dungeon.f7.terms;
 
+import blade.addon.utils.Misc;
 import blade.addon.utils.Scheduler;
 import blade.addon.utils.config.values.Floor7;
 import blade.addon.utils.dungeon.Phase;
@@ -24,7 +25,7 @@ public class Pre4Notifier {
         Events.ON_TERMINAL.register((name, objective) -> {
             if (!Floor7.notifyPre4Completion) return;
 
-            if (objective.equals("device") && atDev()) {
+            if (objective.equals("device") && Misc.isClientPlayer(name) && atDev()) {
                 completedTime = System.currentTimeMillis();
                 showNotification = true;
                 Scheduler.scheduleSound(SoundEvents.BLOCK_NOTE_BLOCK_PLING.value(), 1, 1);
@@ -32,7 +33,7 @@ public class Pre4Notifier {
         });
     }
 
-    private static boolean atDev() {
+    public static boolean atDev() {
         ClientPlayerEntity player = MinecraftClient.getInstance().player;
         if (player == null) return false;
         return (player.getX() >= 63 && player.getX() <= 64 && player.getY() == 127 && player.getZ() >= 35 && player.getZ() <= 36);
diff --git a/src/main/java/blade/addon/features/dungeon/f7/TermStartTimer.java b/src/main/java/blade/addon/features/dungeon/f7/terms/TermStartTimer.java
similarity index 96%
rename from src/main/java/blade/addon/features/dungeon/f7/TermStartTimer.java
rename to src/main/java/blade/addon/features/dungeon/f7/terms/TermStartTimer.java
index 9756a62..ba270c3 100644
--- a/src/main/java/blade/addon/features/dungeon/f7/TermStartTimer.java
+++ b/src/main/java/blade/addon/features/dungeon/f7/terms/TermStartTimer.java
@@ -1,4 +1,4 @@
-package blade.addon.features.dungeon.f7;
+package blade.addon.features.dungeon.f7.terms;
 
 import blade.addon.utils.Constants;
 import blade.addon.utils.Location;
diff --git a/src/main/java/blade/addon/features/dungeon/f7/terms/TitleHider.java b/src/main/java/blade/addon/features/dungeon/f7/terms/TitleHider.java
new file mode 100644
index 0000000..92e7cf2
--- /dev/null
+++ b/src/main/java/blade/addon/features/dungeon/f7/terms/TitleHider.java
@@ -0,0 +1,13 @@
+package blade.addon.features.dungeon.f7.terms;
+
+import blade.addon.utils.Location;
+import blade.addon.utils.config.values.Floor7;
+import blade.addon.utils.dungeon.Phase;
+
+public class TitleHider {
+
+    public static boolean shouldHideTitle() {
+        return Floor7.hideTerminalTitles && Phase.inP3() && Location.inDungeon();
+    }
+
+}
diff --git a/src/main/java/blade/addon/features/highlight/MobHighlight.java b/src/main/java/blade/addon/features/highlight/MobHighlight.java
index a8c4d72..327729c 100644
--- a/src/main/java/blade/addon/features/highlight/MobHighlight.java
+++ b/src/main/java/blade/addon/features/highlight/MobHighlight.java
@@ -95,6 +95,9 @@ public class MobHighlight {
     @ConfigValue
     public static boolean highlightMimicChests = true;
 
+    @ConfigValue
+    public static int outlineWidth = 4;
+
     @ConfigValue
     public static int starFilledColor = 0xff00ff00;
     @ConfigValue
@@ -458,7 +461,7 @@ public class MobHighlight {
 
         VertexConsumerProvider consumers = worldRenderContext.consumers();
         if (consumers == null) return;
-        VertexConsumer outlineConsumer = consumers.getBuffer(RenderLayers.OUTLINE_ENTITY_LAYER);
+        VertexConsumer outlineConsumer = consumers.getBuffer(RenderLayers.getOutline(outlineWidth));
 
 
         trackedMobs.forEach((entity, mobType) -> {
diff --git a/src/main/java/blade/addon/features/item/DropAnimation.java b/src/main/java/blade/addon/features/item/DropAnimation.java
new file mode 100644
index 0000000..3af72bf
--- /dev/null
+++ b/src/main/java/blade/addon/features/item/DropAnimation.java
@@ -0,0 +1,58 @@
+package blade.addon.features.item;
+
+import blade.addon.utils.Location;
+import blade.addon.utils.config.values.Dungeons;
+import net.fabricmc.fabric.api.client.event.lifecycle.v1.ClientTickEvents;
+import net.minecraft.item.ItemStack;
+import net.minecraft.item.Items;
+
+public class DropAnimation {
+
+    private static final int FIX_IN_TICKS = 20;
+
+    private static int scheduledFix = 0;
+    private static ItemStack dropped;
+    private static int selectedSlot;
+    private static boolean cleared = false;
+
+    public static void init() {
+        ClientTickEvents.END_CLIENT_TICK.register(client -> {
+            if (scheduledFix == 0) {
+                clearData();
+            } else if (dropped != null){
+                scheduledFix--;
+            }
+        });
+    }
+
+    public static void clearData() {
+        if (cleared) return;
+        setDroppedData(null, -1);
+        cleared = true;
+    }
+
+    public static void setDroppedData(ItemStack itemStack, int slot) {
+        if (itemStack == null) {
+            dropped = null;
+            selectedSlot = -1;
+            return;
+        }
+        if (itemStack.getItem() == Items.AIR) return;
+        dropped = itemStack;
+        selectedSlot = slot;
+        scheduledFix = FIX_IN_TICKS;
+        cleared = false;
+    }
+
+    public static ItemStack getPrevDroppedItem() {
+        return dropped;
+    }
+
+    public static int getSelectedSlot() {
+        return selectedSlot;
+    }
+
+    public static boolean shouldProceed() {
+        return Dungeons.disableDropAnimation && Location.inDungeon();
+    }
+}
diff --git a/src/main/java/blade/addon/features/item/ProtectItem.java b/src/main/java/blade/addon/features/item/ProtectItem.java
index 3f886ae..26e5a44 100644
--- a/src/main/java/blade/addon/features/item/ProtectItem.java
+++ b/src/main/java/blade/addon/features/item/ProtectItem.java
@@ -124,7 +124,7 @@ public class ProtectItem {
             Slot slot = slots.get(49);
             ItemStack stack = slot.getStack();
             Text itemName = stack.getName();
-            if (itemName != null && itemName.getString().contains("Sell Item")) {
+            if (itemName != null && itemName.getString().contains("Sell Item") || Misc.containsLore(stack, "Click to buyback!")) {
                 Misc.addChatMessage(Text.literal("Protected ").append(item.getName()).append(" From being sold"));
                 return true;
             }
diff --git a/src/main/java/blade/addon/features/other/CompactHoppity.java b/src/main/java/blade/addon/features/other/CompactHoppity.java
new file mode 100644
index 0000000..1969cb8
--- /dev/null
+++ b/src/main/java/blade/addon/features/other/CompactHoppity.java
@@ -0,0 +1,49 @@
+package blade.addon.features.other;
+
+import blade.addon.utils.Misc;
+import blade.addon.utils.config.values.ExtraOptions;
+import blade.addon.utils.events.Events;
+import net.minecraft.text.Text;
+
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+public class CompactHoppity {
+
+    private static final Pattern NAME_PATTERN = Pattern.compile("^§D§LHOPPITY'S HUNT §7You found (.+) §.\\(.+\\)!$");
+    private static final Pattern INFO_PATTERN = Pattern.compile("^(§.§.)(DUPLICATE|NEW) RABBIT! (.+)");
+    private static String formattedName = "";
+
+    public static void init() {
+
+        Events.ON_CANCELABLE_GAME_MESSAGE.register(text -> {
+            if (!ExtraOptions.compactHoppityMsgs) return false;
+
+            String string = text.getString();
+            if (string.equals("HOPPITY'S HUNT You found a Hitman Egg!")) {
+                return true;
+            }
+
+            Matcher matcher = NAME_PATTERN.matcher(string);
+            if (matcher.find()) {
+                formattedName = matcher.group(1);
+                return true;
+            }
+
+            matcher = INFO_PATTERN.matcher(string);
+            if (matcher.find()) {
+                String styleInfo = matcher.group(1);
+                String statusInfo = matcher.group(2);
+                String chocolateAmount = matcher.group(3);
+                Misc.addChatMessage(Text.literal(styleInfo + statusInfo + " " + formattedName + " " + chocolateAmount));
+                formattedName = "";
+                return true;
+            }
+
+            return false;
+
+        });
+
+    }
+
+}
diff --git a/src/main/java/blade/addon/features/other/InventoryButton.java b/src/main/java/blade/addon/features/other/InventoryButton.java
new file mode 100644
index 0000000..c53e00b
--- /dev/null
+++ b/src/main/java/blade/addon/features/other/InventoryButton.java
@@ -0,0 +1,64 @@
+package blade.addon.features.other;
+
+import blade.addon.utils.Misc;
+import net.minecraft.client.MinecraftClient;
+import net.minecraft.client.font.TextRenderer;
+import net.minecraft.client.gl.RenderPipelines;
+import net.minecraft.client.gui.DrawContext;
+import net.minecraft.util.Identifier;
+
+import java.util.ArrayList;
+import java.util.function.Supplier;
+
+public class InventoryButton {
+
+    private static final Identifier BUTTON_TEXTURE = Identifier.ofVanilla("widget/button");
+    private static final int SIZE = 18;
+    private static final ArrayList<InventoryButton> inventoryButtons = new ArrayList<>();
+
+    private final int x;
+    private final int y;
+    private final int index;
+    private final Supplier<String> command;
+
+    public InventoryButton(int x, int y, Supplier<String> command) {
+        this.x = x;
+        this.y = y;
+        this.command = command;
+        inventoryButtons.add(this);
+        this.index = inventoryButtons.size();
+    }
+
+    public void render(DrawContext context, int mouseX, int mouseY, float deltaTicks) {
+        String str = command.get();
+        if (str == null ||str.isEmpty()) return;
+        context.drawGuiTexture(RenderPipelines.GUI_TEXTURED, BUTTON_TEXTURE, x, y, SIZE, SIZE);
+        TextRenderer textRenderer = MinecraftClient.getInstance().textRenderer;
+        int center = textRenderer.getWidth("" + index);
+        context.drawText(textRenderer, "" + index, x + (SIZE - center) / 2 + 1, y + (SIZE - textRenderer.fontHeight) / 2 + 1, 0xffffffff, true);
+    }
+
+    public void onClick(double mouseX, double mouseY, int button) {
+        String str = command.get();
+        if (str == null ||str.isEmpty()) return;
+        Misc.executeCommand(str);
+    }
+
+    public boolean inBounds(double mouseX, double mouseY) {
+        return mouseX >= x && mouseX <= x + SIZE && mouseY >= y && mouseY <= y + SIZE;
+    }
+
+    public static void parseClicks(double mouseX, double mouseY, int button) {
+        for (InventoryButton inventoryButton : inventoryButtons) {
+            if (inventoryButton.inBounds(mouseX, mouseY)) {
+                inventoryButton.onClick(mouseX, mouseY, button);
+            }
+        }
+    }
+
+    public static void renderAll(DrawContext context, int mouseX, int mouseY, float deltaTicks) {
+        for (InventoryButton button : inventoryButtons) {
+            button.render(context, mouseX, mouseY, deltaTicks);
+        }
+    }
+}
diff --git a/src/main/java/blade/addon/features/other/PracticeSS.java b/src/main/java/blade/addon/features/other/PracticeSS.java
index 0451931..bc7be4e 100644
--- a/src/main/java/blade/addon/features/other/PracticeSS.java
+++ b/src/main/java/blade/addon/features/other/PracticeSS.java
@@ -45,10 +45,12 @@ public class PracticeSS {
     private static final CopyOnWriteArrayList<BlockPos> buttons = new CopyOnWriteArrayList<>();
 
     private static BlockPos startPos = null;
+    private static Direction direction = null;
 
-    private static final long SKIP_TIME = 500;
-    private static final int LAST_SHOWN_DURATION = 11;
-    private static final int UNLUCKY_DURATION = 35;
+    private static final long SKIP_TIME_MS = 500;
+    private static final int LAST_SHOWN_DURATION = 9;
+    private static final int UNLUCKY_DURATION = 36;
+    private static final int START_WAIT_DURATION = 6;
 
     private static boolean started = false;
     private static boolean showingPattern = false;
@@ -57,6 +59,7 @@ public class PracticeSS {
     private static int endIndex = 4;
 
     private static int ticksLeft = 0;
+    private static int totalTicks = 0;
 
     private static long startTime = 0;
 
@@ -68,8 +71,6 @@ public class PracticeSS {
     private static boolean wasLuckyButton = false;
     private static int ticksSinceLuckyButton = 0;
 
-    private static Direction direction = null;
-
     public static void init() {
 
         Events.ON_BLOCK_INTERACTION.register((BlockHitResult result, ItemStack item) -> {
@@ -83,53 +84,11 @@ public class PracticeSS {
             if (!isValidBlock(block)) return false;
 
             if (isStartButton(pos)) {
-                if (started && ExtraOptions.autoSkip) {
-                    start(world, state, pos);
-                    startPos = pos;
-                } else if (inSkipPhase || System.currentTimeMillis() - skippedTime < SKIP_TIME) {
-                    clicks++;
-                    if (clicks >= 2 && inSkipPhase) {
-                        skipped = true;
-                        skippedTime = System.currentTimeMillis();
-                    }
-                } else {
-                    start(world, state, pos);
-                    startPos = pos;
-                }
+                parseStartButton(world, state, pos);
             }
 
             if (started && !showingPattern) {
-                if (currentIndex == 1 && ticksSinceLuckyButton > 0) {
-                    return ExtraOptions.blockUnluckyButtonClick;
-                }
-
-                BlockPos nextButton = buttons.get(currentIndex);
-                if (samePosition(pos, nextButton)) {
-                    currentIndex++;
-
-                    if (currentIndex == 5) {
-                        if (ExtraOptions.realisticDelay) {
-                            if (wasLuckyButton) {
-                                PersonalBests.practiseSSRealisticLuckyTime.testNewTime(Text.literal("SS with Realistic Time (Lucky Button) Took: "), startTime);
-                            } else {
-                                PersonalBests.practiseSSRealisticTime.testNewTime(Text.literal("SS with Realistic Time Took: "), startTime);
-                            }
-
-                        } else {
-                            if (wasLuckyButton) {
-                                PersonalBests.practiseSSLuckyTime.testNewTime(Text.literal("SS (Lucky Button) Took: "), startTime);
-                            } else {
-                                PersonalBests.practiseSSTime.testNewTime(Text.literal("SS Took: "), startTime);
-                            }
-
-                        }
-
-
-                        reset();
-                    } else if (currentIndex == endIndex) {
-                        incStage();
-                    }
-                }
+                return parseNormalButton(pos);
             }
 
             return false;
@@ -144,7 +103,7 @@ public class PracticeSS {
                 }
             }
 
-            if (System.currentTimeMillis() - startTime > SKIP_TIME && inSkipPhase) {
+            if (System.currentTimeMillis() - startTime > SKIP_TIME_MS && inSkipPhase) {
                 inSkipPhase = false;
 
                 if (skipped) {
@@ -163,6 +122,41 @@ public class PracticeSS {
 
     }
 
+    private static void parseStartButton(ClientWorld world, BlockState state, BlockPos pos) {
+        if (started && ExtraOptions.autoSkip) {
+            start(world, state, pos);
+            startPos = pos;
+        } else if (inSkipPhase || System.currentTimeMillis() - skippedTime < SKIP_TIME_MS) {
+            clicks++;
+            if (clicks >= 2 && inSkipPhase) {
+                skipped = true;
+                skippedTime = System.currentTimeMillis();
+            }
+        } else {
+            start(world, state, pos);
+            startPos = pos;
+        }
+    }
+
+    private static boolean parseNormalButton(BlockPos pos) {
+        if (currentIndex == 1 && ticksSinceLuckyButton > 0) {
+            return ExtraOptions.blockUnluckyButtonClick;
+        }
+
+        BlockPos nextButton = buttons.get(currentIndex);
+        if (samePosition(pos, nextButton)) {
+            currentIndex++;
+
+            if (currentIndex == 5) {
+                recordTime();
+            } else if (currentIndex == endIndex) {
+                incStage();
+            }
+        }
+
+        return false;
+    }
+
     private static void start(ClientWorld world, BlockState state, BlockPos pos) {
         reset();
         direction = state.get(Properties.HORIZONTAL_FACING);
@@ -247,6 +241,7 @@ public class PracticeSS {
     }
 
     private static boolean isStartButton(BlockPos pos) {
+        if (samePosition(pos, ExtraOptions.startButton)) return true;
         for (BlockPos startPos : START_POSES) {
             if (samePosition(pos, startPos)) return true;
         }
@@ -255,9 +250,38 @@ public class PracticeSS {
     }
 
     private static boolean samePosition(BlockPos pos1, BlockPos pos2) {
+        if (pos1 == null || pos2 == null) return false;
         return pos1.getX() == pos2.getX() && pos1.getY() == pos2.getY() && pos1.getZ() == pos2.getZ();
     }
 
+    private static void recordTime() {
+        if (ExtraOptions.realisticDelay) {
+            if (ExtraOptions.includeLuckyButton) {
+                if (wasLuckyButton) {
+                    PersonalBests.practiseSSRealisticTime.testNewTime(Text.literal("SS with Realistic Time (Lucky Button) Took: "), startTime);
+                } else {
+                    PersonalBests.practiseSSRealisticUnluckyTime.testNewTime(Text.literal("SS with Realistic Time Took: "), startTime);
+                }
+            } else {
+                PersonalBests.practiseSSRealisticTime.testNewTime(Text.literal("SS with Realistic Time Took: "), startTime);
+            }
+
+        } else {
+            if (ExtraOptions.includeLuckyButton) {
+                if (wasLuckyButton) {
+                    PersonalBests.practiseSSTime.testNewTime(Text.literal("SS (Lucky Button) Took: "), startTime);
+                } else {
+                    PersonalBests.practiseSSUnluckyTime.testNewTime(Text.literal("SS Time Took: "), startTime);
+                }
+            } else {
+                PersonalBests.practiseSSTime.testNewTime(Text.literal("SS Took: "), startTime);
+            }
+
+        }
+
+        reset();
+    }
+
     private static void reset() {
         started = false;
         showingPattern = false;
@@ -267,6 +291,7 @@ public class PracticeSS {
         ticksSinceLuckyButton = 0;
         wasLuckyButton = false;
         direction = null;
+        totalTicks = 0;
     }
 
     private static int getColor(int index) {
@@ -289,7 +314,14 @@ public class PracticeSS {
 
         endIndex = stage;
         currentIndex = 0;
-        ticksLeft = getDelay() * endIndex + (ExtraOptions.realisticDelay ? stage != 2 ? LAST_SHOWN_DURATION : 4 : 0);
+        totalTicks = getDelay() * endIndex;
+        if (ExtraOptions.realisticDelay) {
+            totalTicks += LAST_SHOWN_DURATION;
+            if (stage != 2) {
+                totalTicks += START_WAIT_DURATION;
+            }
+        }
+        ticksLeft = totalTicks;
         showingPattern = true;
     }
 
@@ -325,9 +357,11 @@ public class PracticeSS {
 
 
         if (showingPattern) {
-            int lastIndex = getLastDisplayIndex();
-            renderButtons(matrixStack, buffer, 0, lastIndex);
-            renderBackground(matrixStack, buffer, lastIndex - 1);
+            if (!ExtraOptions.realisticDelay || totalTicks - ticksLeft > START_WAIT_DURATION || endIndex != 2) {
+                int lastIndex = getLastDisplayIndex();
+                renderButtons(matrixStack, buffer, 0, lastIndex);
+                renderBackground(matrixStack, buffer, lastIndex - 1);
+            }
         } else {
             renderButtons(matrixStack, buffer, currentIndex, endIndex);
         }
diff --git a/src/main/java/blade/addon/features/other/RagDisplay.java b/src/main/java/blade/addon/features/other/RagDisplay.java
index a51798c..f4f5e8b 100644
--- a/src/main/java/blade/addon/features/other/RagDisplay.java
+++ b/src/main/java/blade/addon/features/other/RagDisplay.java
@@ -1,6 +1,7 @@
 package blade.addon.features.other;
 
 import blade.addon.utils.Constants;
+import blade.addon.utils.Scheduler;
 import blade.addon.utils.config.values.ExtraOptions;
 import blade.addon.utils.events.Events;
 import blade.addon.utils.rendering.RenderUtils;
@@ -23,16 +24,24 @@ public class RagDisplay {
 
     public static void init() {
         Events.ON_SOUND.register((soundEvent, volume, pitch) -> {
-            if (!ExtraOptions.enableRagaxeDisplay) return;
-            if (soundEvent != SOUND) return;
-            if (volume != 1 && pitch != 1.4920635223388672) return;
+            if (!ExtraOptions.enableRagaxeDisplay && !ExtraOptions.useCustomRagSound) return false;
+            if (soundEvent != SOUND) return false;
+            if (volume != 1 && pitch != 1.4920635223388672) return false;
 
             ClientPlayerEntity player = MinecraftClient.getInstance().player;
-            if (player == null) return;
+            if (player == null) return false;
 
-            if (player.getMainHandStack().getName().getString().contains("Ragnarock")) {
+            if (!player.getMainHandStack().getName().getString().contains("Ragnarock")) return false;
+
+            if (ExtraOptions.enableRagaxeDisplay) {
                 tick = TOTAL_TICKS;
             }
+
+            if (ExtraOptions.useCustom
…… diff 过长已截断 ……
fixed small logic error with block unlucky button clickGabriel Ellnebrand · 2025-12-23 · 82530e9
查看变更
src/main/java/blade/addon/features/other/PracticeSS.java | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/main/java/blade/addon/features/other/PracticeSS.java b/src/main/java/blade/addon/features/other/PracticeSS.java
index 4b1cc0a..0451931 100644
--- a/src/main/java/blade/addon/features/other/PracticeSS.java
+++ b/src/main/java/blade/addon/features/other/PracticeSS.java
@@ -100,7 +100,7 @@ public class PracticeSS {
 
             if (started && !showingPattern) {
                 if (currentIndex == 1 && ticksSinceLuckyButton > 0) {
-                    return !ExtraOptions.blockUnluckyButtonClick;
+                    return ExtraOptions.blockUnluckyButtonClick;
                 }
 
                 BlockPos nextButton = buttons.get(currentIndex);
- added location notifier - added star count - add protect item /ba protectitem (unsure how good it is inside dungeons) - added pbs - added toggle scrollwheel for hotbar - fixed waypoints not saving if placed or removed by clicking on a block - added kicked timer - added pre4 completion notification - added hide titles on pre4 dev - added pillar explode timer - added storm crushed notification - added melody warning notification (like the valley one) - added ragnarock display - added leap notification - added an ss practice mode - added hide blaze nametagsGabriel Ellnebrand · 2025-12-23 · f90531d
查看变更
gradle.properties                                                                             |   4 +-
 src/main/java/blade/addon/Bladeaddons.java                                                    |  26 +++-
 src/main/java/blade/addon/features/dungeon/AutoRequeue.java                                   |  44 +++----
 src/main/java/blade/addon/features/dungeon/f7/BossWaypoints.java                              |   5 +-
 src/main/java/blade/addon/features/dungeon/f7/CrystalSpawn.java                               |  54 +++++++-
 src/main/java/blade/addon/features/dungeon/f7/DragSpawnTimer.java                             |   4 +-
 src/main/java/blade/addon/features/dungeon/f7/LeapNotification.java                           | 102 +++++++++++++++
 src/main/java/blade/addon/features/dungeon/f7/LocationNotifier.java                           |  61 +++++++++
 src/main/java/blade/addon/features/dungeon/f7/MelodyWarning.java                              |  84 +++++++++++++
 src/main/java/blade/addon/features/dungeon/f7/PillarExplode.java                              |  65 ++++++++++
 src/main/java/blade/addon/features/dungeon/f7/Pre4Notifier.java                               |  57 +++++++++
 src/main/java/blade/addon/features/dungeon/f7/PredevTimer.java                                |  51 ++++++++
 src/main/java/blade/addon/features/dungeon/f7/RelicTimer.java                                 | 125 ++++++++++++++++---
 src/main/java/blade/addon/features/{dungeon => highlight}/MobHighlight.java                   |   6 +-
 src/main/java/blade/addon/features/item/ItemRarity.java                                       |   4 +-
 src/main/java/blade/addon/features/item/ItemRarityHighlight.java                              |  18 +--
 src/main/java/blade/addon/features/item/ProtectItem.java                                      | 150 ++++++++++++++++++++++
 src/main/java/blade/addon/features/item/ProtectedItemHolder.java                              |  14 +++
 src/main/java/blade/addon/features/item/StarCountHighlight.java                               |  45 +++++++
 src/main/java/blade/addon/features/item/StarCountHolder.java                                  |  15 +++
 src/main/java/blade/addon/features/other/KickedTimer.java                                     |  45 +++++++
 src/main/java/blade/addon/features/other/PracticeSS.java                                      | 390 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 src/main/java/blade/addon/features/other/RagDisplay.java                                      |  53 ++++++++
 src/main/java/blade/addon/mixin/BlockEntityRenderMixin.java                                   |   2 +-
 src/main/java/blade/addon/mixin/ClientPlayerEntityMixin.java                                  |  28 +++++
 src/main/java/blade/addon/mixin/EntityRendererMixin.java                                      |  24 ++++
 src/main/java/blade/addon/mixin/HandledScreenMixin.java                                       |  56 ++++++++-
 src/main/java/blade/addon/mixin/InGameHudMixin.java                                           |  21 +++-
 src/main/java/blade/addon/mixin/ItemStackMixin.java                                           |  48 +++++++-
 src/main/java/blade/addon/mixin/MinecraftClientMixin.java                                     |   8 +-
 src/main/java/blade/addon/mixin/MouseMixin.java                                               |  19 +++
 src/main/java/blade/addon/mixin/NetworkMixin.java                                             |  61 ++++++++-
 src/main/java/blade/addon/mixin/PlayerEntityMixin.java                                        |  23 ++++
 src/main/java/blade/addon/mixin/ServerTickMixin.java                                          |   1 -
 src/main/java/blade/addon/utils/Commands.java                                                 |  19 ++-
 src/main/java/blade/addon/utils/Constants.java                                                |   1 +
 src/main/java/blade/addon/utils/Debug.java                                                    |  20 ++-
 src/main/java/blade/addon/utils/Keybinds.java                                                 |  27 ++++
 src/main/java/blade/addon/utils/Location.java                                                 |  12 ++
 src/main/java/blade/addon/utils/Misc.java                                                     |  10 ++
 src/main/java/blade/addon/utils/Scheduler.java                                                |   7 +-
 src/main/java/blade/addon/utils/config/Config.java                                            |  51 +++++++-
 src/main/java/blade/addon/utils/config/FolderUtility.java                                     |  32 +++++
 src/main/java/blade/addon/utils/config/components/CombinedScreenNotifications.java            |  18 ++-
 src/main/java/blade/addon/utils/config/components/Components.java                             |  38 +++++-
 src/main/java/blade/addon/utils/config/values/Dungeons.java                                   |   6 +
 src/main/java/blade/addon/utils/config/values/ExtraOptions.java                               |  39 ++++++
 src/main/java/blade/addon/utils/config/values/Floor7.java                                     |  45 +++++++
 src/main/java/blade/addon/utils/detection/Detection.java                                      |  17 +++
 src/main/java/blade/addon/utils/detection/PlayerDataHolder.java                               |  11 ++
 src/main/java/blade/addon/utils/dungeon/DungeonClass.java                                     |  54 +++++++-
 src/main/java/blade/addon/utils/dungeon/Phase.java                                            |   7 ++
 src/main/java/blade/addon/utils/dungeon/Section.java                                          |  34 ++---
 src/main/java/blade/addon/utils/events/CustomEvents.java                                      |  43 +++++++
 src/main/java/blade/addon/utils/events/Events.java                                            |  13 +-
 src/main/java/blade/addon/utils/events/interfaces/{EntityTrackEvent.java => EntityEvent.java} |   4 +-
 src/main/java/blade/addon/utils/events/interfaces/PartyMessageEvent.java                      |   7 ++
 src/main/java/blade/addon/utils/events/interfaces/PlaySoundEvent.java                         |   7 ++
 src/main/java/blade/addon/utils/events/interfaces/SectionEvent.java                           |   5 +
 src/main/java/blade/addon/utils/events/interfaces/TerminalEvent.java                          |   5 +
 src/main/java/blade/addon/utils/rendering/RenderUtils.java                                    |   2 +
 src/main/java/blade/addon/utils/times/PersonalBest.java                                       |  60 +++++++++
 src/main/java/blade/addon/utils/times/PersonalBests.java                                      |  61 +++++++++
 src/main/resources/assets/blade-addons/textures/gui/sprites/lock.png                          | Bin 0 -> 852 bytes
 src/main/resources/assets/blade-addons/textures/gui/sprites/pets/crow.png                     | Bin 0 -> 3882 bytes
 src/main/resources/assets/blade-addons/textures/gui/sprites/pets/rose-dragon.png              | Bin 0 -> 4333 bytes
 src/main/resources/blade-addons.mixins.json                                                   |   4 +
 67 files changed, 2249 insertions(+), 123 deletions(-)
diff --git a/gradle.properties b/gradle.properties
index 407d8e9..157c1a8 100644
--- a/gradle.properties
+++ b/gradle.properties
@@ -13,12 +13,12 @@ loader_version=0.17.3
 loom_version=1.13-SNAPSHOT
 
 # Mod Properties
-mod_version=2.2.4
+mod_version=2.2.5
 maven_group=blade.addon
 archives_base_name=blade-addons
 
 # Dependencies
 fabric_version=0.136.0+1.21.8
-practical_config=963fde881f048a8ffdcb1468ba076a7985d135c1
+practical_config=1.3.5
 modmenu_version = 15.0.0
 hypixel_api_version = 1.0.1
\ No newline at end of file
diff --git a/src/main/java/blade/addon/Bladeaddons.java b/src/main/java/blade/addon/Bladeaddons.java
index 03ce519..3c4af01 100644
--- a/src/main/java/blade/addon/Bladeaddons.java
+++ b/src/main/java/blade/addon/Bladeaddons.java
@@ -1,6 +1,5 @@
 package blade.addon;
 
-import blade.addon.features.other.DianaNotifier;
 import blade.addon.features.dungeon.AutoRequeue;
 import blade.addon.features.dungeon.ChestCounter;
 import blade.addon.features.dungeon.DeathTickTimer;
@@ -10,7 +9,6 @@ import blade.addon.features.dungeon.HidePlayers;
 import blade.addon.features.dungeon.ItemHighlight;
 import blade.addon.features.dungeon.KeyNotifier;
 import blade.addon.features.dungeon.LeapMessage;
-import blade.addon.features.dungeon.MobHighlight;
 import blade.addon.features.dungeon.SecretSpawnTimer;
 import blade.addon.features.dungeon.WarpCooldown;
 import blade.addon.features.dungeon.f7.BossWaypoints;
@@ -19,10 +17,21 @@ import blade.addon.features.dungeon.f7.DistanceToLedge;
 import blade.addon.features.dungeon.f7.DragSpawnTimer;
 import blade.addon.features.dungeon.f7.GoldorTickTimer;
 import blade.addon.features.dungeon.f7.InvincibilityTimer;
+import blade.addon.features.dungeon.f7.LeapNotification;
+import blade.addon.features.dungeon.f7.LocationNotifier;
+import blade.addon.features.dungeon.f7.MelodyWarning;
+import blade.addon.features.dungeon.f7.PillarExplode;
 import blade.addon.features.dungeon.f7.PositionMessages;
+import blade.addon.features.dungeon.f7.Pre4Notifier;
+import blade.addon.features.dungeon.f7.PredevTimer;
 import blade.addon.features.dungeon.f7.RelicTimer;
 import blade.addon.features.dungeon.f7.StormTickTimer;
 import blade.addon.features.dungeon.f7.TermStartTimer;
+import blade.addon.features.highlight.MobHighlight;
+import blade.addon.features.other.DianaNotifier;
+import blade.addon.features.other.KickedTimer;
+import blade.addon.features.other.PracticeSS;
+import blade.addon.features.other.RagDisplay;
 import blade.addon.features.other.SelectedPet;
 import blade.addon.utils.Commands;
 import blade.addon.utils.Debug;
@@ -30,18 +39,22 @@ import blade.addon.utils.Keybinds;
 import blade.addon.utils.Location;
 import blade.addon.utils.Scheduler;
 import blade.addon.utils.config.Config;
+import blade.addon.utils.config.FolderUtility;
 import blade.addon.utils.config.components.Components;
 import blade.addon.utils.dungeon.DungeonClass;
 import blade.addon.utils.dungeon.Phase;
 import blade.addon.utils.dungeon.Section;
+import blade.addon.utils.events.CustomEvents;
 import net.fabricmc.api.ModInitializer;
 
 public class Bladeaddons implements ModInitializer {
 	@Override
 	public void onInitialize() {
+        FolderUtility.init();
         Components.init();
         Config.manager.load();
 		Keybinds.init();
+        CustomEvents.init();
         Commands.init();
         Debug.init();
 		Location.init();
@@ -73,5 +86,14 @@ public class Bladeaddons implements ModInitializer {
         DianaNotifier.init();
         DragSpawnTimer.init();
         SelectedPet.init();
+        LocationNotifier.init();
+        PredevTimer.init();
+        KickedTimer.init();
+        Pre4Notifier.init();
+        PillarExplode.init();
+        MelodyWarning.init();
+        RagDisplay.init();
+        LeapNotification.init();
+        PracticeSS.init();
     }
 }
\ No newline at end of file
diff --git a/src/main/java/blade/addon/features/dungeon/AutoRequeue.java b/src/main/java/blade/addon/features/dungeon/AutoRequeue.java
index 47223ad..6bfb950 100644
--- a/src/main/java/blade/addon/features/dungeon/AutoRequeue.java
+++ b/src/main/java/blade/addon/features/dungeon/AutoRequeue.java
@@ -8,6 +8,7 @@ import net.fabricmc.fabric.api.client.message.v1.ClientReceiveMessageEvents;
 import net.minecraft.client.MinecraftClient;
 import net.minecraft.client.gui.hud.ChatHud;
 import net.minecraft.text.Text;
+import net.minecraft.util.Formatting;
 
 import java.util.HashMap;
 import java.util.regex.Matcher;
@@ -15,51 +16,38 @@ import java.util.regex.Pattern;
 
 public class AutoRequeue {
 
-    private static final Pattern PATTERN = Pattern.compile("^§9Party §8>");
     private static final Pattern DT_PATTERN = Pattern.compile("!dt");
     private static final Pattern UNDT_PATTERN = Pattern.compile("!undt");
     private static final Pattern LEFT_PATTERN = Pattern.compile("has left the party.$");
 
-    private static final int MESSAGE_OFFSET = 2;
-
-    private static final HashMap<String, Text> playerHashMap = new HashMap<>();
+    private static final HashMap<String, String> playerHashMap = new HashMap<>();
 
 
     private static boolean someoneLeft = false;
 
     public static void init() {
-        ClientReceiveMessageEvents.GAME.register((message, overlay) -> {
-            if (!Location.inDungeon() && !Dungeons.enableAutoRequeue) return;
-            String string = message.getString();
-            Matcher matcher = LEFT_PATTERN.matcher(string);
-            if (matcher.find()) {
-                someoneLeft = true;
-                return;
-            }
-
-            matcher = PATTERN.matcher(string);
-
-            if (!matcher.find()) return;
-
-            int startOfMessage = string.indexOf(":");
-
-            if (startOfMessage == -1 || startOfMessage + MESSAGE_OFFSET >= string.length()) return;
-
-            String realMessage = string.substring(startOfMessage + MESSAGE_OFFSET);
-            String username = string.substring(0, startOfMessage - MESSAGE_OFFSET);
-
+        Events.ON_PARTY_MESSAGE.register((username, message) -> {
             if (playerHashMap.containsKey(username)) {
-                matcher = UNDT_PATTERN.matcher(realMessage);
+                Matcher matcher = UNDT_PATTERN.matcher(message);
                 if (matcher.find()) {
                     playerHashMap.remove(username);
+                    Misc.addChatMessage(Text.literal("Downtime removed").formatted(Formatting.GREEN));
                 }
             } else {
-                matcher = DT_PATTERN.matcher(realMessage);
+                Matcher matcher = DT_PATTERN.matcher(message);
                 if (matcher.find()) {
                     playerHashMap.put(username, message);
+                    Misc.addChatMessage(Text.literal("Downtime added").formatted(Formatting.GREEN));
                 }
             }
-
+        });
+        ClientReceiveMessageEvents.GAME.register((message, overlay) -> {
+            if (!Location.inDungeon() && !Dungeons.enableAutoRequeue) return;
+            String string = message.getString();
+            Matcher matcher = LEFT_PATTERN.matcher(string);
+            if (matcher.find()) {
+                someoneLeft = true;
+            }
         });
 
         Events.ON_RUN_END.register(() -> {
@@ -70,7 +58,7 @@ public class AutoRequeue {
             } else {
                 Misc.addChatMessage(Text.literal("Downtime reasons:"));
                 ChatHud chathud = MinecraftClient.getInstance().inGameHud.getChatHud();
-                playerHashMap.forEach((name, text) -> chathud.addMessage(text));
+                playerHashMap.forEach((name, string) -> chathud.addMessage(Text.literal(name + "> " + string)));
 
 
             }
diff --git a/src/main/java/blade/addon/features/dungeon/f7/BossWaypoints.java b/src/main/java/blade/addon/features/dungeon/f7/BossWaypoints.java
index 889fd3f..091927c 100644
--- a/src/main/java/blade/addon/features/dungeon/f7/BossWaypoints.java
+++ b/src/main/java/blade/addon/features/dungeon/f7/BossWaypoints.java
@@ -1,9 +1,9 @@
 package blade.addon.features.dungeon.f7;
 
-import blade.addon.utils.Constants;
 import blade.addon.utils.Location;
 import blade.addon.utils.Misc;
 import blade.addon.utils.Waypoint;
+import blade.addon.utils.config.FolderUtility;
 import blade.addon.utils.config.values.Floor7;
 import blade.addon.utils.dungeon.Phase;
 import blade.addon.utils.rendering.RenderLayers;
@@ -36,7 +36,7 @@ import java.util.concurrent.CopyOnWriteArrayList;
 
 public class BossWaypoints {
 
-    private static final String filePath = "./config/" + Constants.NAMESPACE + "-waypoints.json";
+    private static final String filePath = FolderUtility.OLD_PATH + FolderUtility.WAYPOINTS_NAME;
     private static final String WAYPOINTS_NAME = "waypoints";
 
     public static final CopyOnWriteArrayList<Waypoint> waypoints = new CopyOnWriteArrayList<>();
@@ -134,6 +134,7 @@ public class BossWaypoints {
             waypoints.remove(waypoint);
         }
 
+        save();
         return ActionResult.PASS;
     }
 
diff --git a/src/main/java/blade/addon/features/dungeon/f7/CrystalSpawn.java b/src/main/java/blade/addon/features/dungeon/f7/CrystalSpawn.java
index 895723a..1425848 100644
--- a/src/main/java/blade/addon/features/dungeon/f7/CrystalSpawn.java
+++ b/src/main/java/blade/addon/features/dungeon/f7/CrystalSpawn.java
@@ -2,13 +2,17 @@ package blade.addon.features.dungeon.f7;
 
 import blade.addon.utils.Constants;
 import blade.addon.utils.Location;
+import blade.addon.utils.Misc;
 import blade.addon.utils.config.values.Floor7;
 import blade.addon.utils.dungeon.Phase;
 import blade.addon.utils.events.Events;
 import blade.addon.utils.rendering.RenderUtils;
+import blade.addon.utils.times.PersonalBests;
 import config.practical.hud.HUDComponent;
 import net.minecraft.client.MinecraftClient;
 import net.minecraft.client.gui.DrawContext;
+import net.minecraft.client.network.ClientPlayerEntity;
+import net.minecraft.entity.decoration.EndCrystalEntity;
 import net.minecraft.text.Text;
 import net.minecraft.util.Formatting;
 
@@ -19,6 +23,10 @@ public class CrystalSpawn {
 
     private static final Pattern PATTERN_1 = Pattern.compile("^\\[BOSS] Maxor: THAT BEAM! IT HURTS! IT HURTS!!$");
     private static final Pattern PATTERN_2 = Pattern.compile("^\\[BOSS] Maxor: YOU TRICKED ME!$");
+    private static final Pattern RELIC_PICK_UP = Pattern.compile("(\\w+) picked up an Energy Crystal!$");
+
+    private static long pickupTime = 0;
+    private static boolean pickedUp = false;
 
     private static final int TICK_SPAWN = 34;
     private static int tick = 0;
@@ -29,28 +37,62 @@ public class CrystalSpawn {
             if (!Location.inDungeon() || !Phase.inP1() || !Floor7.enableCrystalSpawnTime) return;
 
             Matcher matcher = PATTERN_1.matcher(text.getString());
-            if (!matcher.find()) {
-                matcher = PATTERN_2.matcher(text.getString());
+            if (matcher.find()) {
+                tick = TICK_SPAWN;
+                return;
+            }
 
-                if (!matcher.find()) return;
+            matcher = PATTERN_2.matcher(text.getString());
+            if (matcher.find()) {
+                tick = TICK_SPAWN;
+                return;
             }
 
-            tick = TICK_SPAWN;
-        });
+            matcher = RELIC_PICK_UP.matcher(text.getString());
+            if (matcher.find()) {
+                String name = matcher.group(1);
+                if (Misc.isClientPlayer(name)) {
+                    pickupTime = System.currentTimeMillis();
+                    pickedUp = true;
+                }
 
+            }
 
+        });
         Events.ON_SERVER_TICK.register(() -> tick = Math.max(tick - 1, 0));
 
         Events.ON_LOCATION_CHANGE.register(newLocation -> {
             if (Location.inDungeon()) {
                 tick = 0;
+                pickedUp = false;
             }
         });
 
+        Events.ON_ENTITY_SPAWNED.register((entity, world) -> {
+            if (!pickedUp || !Location.inDungeon() || !Floor7.enableCrystalSpawnTime || !Phase.inP1()) return;
+            if (entity instanceof EndCrystalEntity crystal) {
+                ClientPlayerEntity player = MinecraftClient.getInstance().player;
+                if (player == null) return;
+
+                double px = player.getX();
+                double cx = crystal.getX();
+                double pz = player.getZ();
+                double cz = crystal.getZ();
+
+                double distance = Misc.getDistance(px, pz, cx, cz);
+
+                if (distance < 5 && crystal.getY() == 224.375) {
+                    PersonalBests.crystalTime.testNewTime(Text.literal("§aCrystal placed in "), pickupTime);
+                    pickedUp = false;
+                }
+
+            }
+
+        });
     }
 
     public static boolean display() {
-        return  tick > 0 && Location.inDungeon() && Floor7.enableCrystalSpawnTime;
+        return tick > 0 && Location.inDungeon() && Floor7.enableCrystalSpawnTime;
     }
 
     public static void render(HUDComponent component, DrawContext context) {
diff --git a/src/main/java/blade/addon/features/dungeon/f7/DragSpawnTimer.java b/src/main/java/blade/addon/features/dungeon/f7/DragSpawnTimer.java
index 7d1b784..ab20430 100644
--- a/src/main/java/blade/addon/features/dungeon/f7/DragSpawnTimer.java
+++ b/src/main/java/blade/addon/features/dungeon/f7/DragSpawnTimer.java
@@ -59,7 +59,7 @@ public class DragSpawnTimer {
 
     public static void init() {
         Events.ON_PARTICLE.register((x, y, z, effect) -> {
-            if (effect.getType() != ParticleTypes.FLAME) return;
+            if (effect.getType() != ParticleTypes.ENCHANT) return;
             Dragon detectedDragon = getDragon(x, y, z);
 
             if (detectedDragon == Dragon.NONE) return;
@@ -77,7 +77,7 @@ public class DragSpawnTimer {
             } else if (detectedDragon != currentDragon && !hasDoneSplit) {
                 currentDragon = getPrio(detectedDragon, currentDragon);
                 prevDragon = currentDragon;
-                dupeTick = DUPE_DELAY + 50;
+                dupeTick = DUPE_DELAY * 2;
                 hasDoneSplit = true;
             }
         });
diff --git a/src/main/java/blade/addon/features/dungeon/f7/LeapNotification.java b/src/main/java/blade/addon/features/dungeon/f7/LeapNotification.java
new file mode 100644
index 0000000..26a07da
--- /dev/null
+++ b/src/main/java/blade/addon/features/dungeon/f7/LeapNotification.java
@@ -0,0 +1,102 @@
+package blade.addon.features.dungeon.f7;
+
+import blade.addon.utils.Location;
+import blade.addon.utils.config.values.Floor7;
+import blade.addon.utils.detection.Detection;
+import blade.addon.utils.dungeon.Phase;
+import blade.addon.utils.rendering.RenderUtils;
+import config.practical.hud.HUDComponent;
+import net.fabricmc.fabric.api.client.event.lifecycle.v1.ClientTickEvents;
+import net.minecraft.client.MinecraftClient;
+import net.minecraft.client.gui.DrawContext;
+import net.minecraft.client.network.ClientPlayerEntity;
+import net.minecraft.client.world.ClientWorld;
+import net.minecraft.entity.Entity;
+import net.minecraft.text.Text;
+import net.minecraft.util.math.Box;
+import net.minecraft.util.math.Vec3d;
+
+import java.util.List;
+
+/**
+ * Ported from valley with minimal changes
+ */
+
+public class LeapNotification {
+
+    private static final Box[] REGIONS = {
+            new Box(90, 106, 50, 111, 145, 123),
+            new Box(17, 106, 121, 108, 145, 143),
+            new Box(-2, 106, 51, 20, 145, 142),
+            new Box(-1, 26, 29, 191, 145, 58),
+            new Box(3, 5, 0, 128, 48, 140)
+    };
+
+    private static final Box EE2_BOX = new Box(57, 108, 130, 59, 110, 132);
+    private static final Box HEE2_BOX = new Box(59, 132, 138, 62, 133, 140);
+    private static final Box EE3_BOX = new Box(1, 108, 103, 3, 110, 105);
+    private static final Box CORE_BOX = new Box(53.5, 114, 49.5, 55.5, 116, 51.5);
+    private static final Box RELIC_BOX = new Box(51.5, 3, 73.5, 57.5, 8, 79.5);
+
+    private static int count = 0;
+    private static int currentSpot = 0;
+    private static boolean inBounds = false;
+
+    public static void init() {
+        ClientTickEvents.END_CLIENT_TICK.register(client -> {
+            if (!Floor7.leapNotifications || !Phase.isInFloor7() || !Location.inDungeon() || !Phase.inBoss()) {
+                inBounds = false;
+                return;
+            }
+
+            ClientPlayerEntity player = client.player;
+            ClientWorld world = client.world;
+
+            if (player == null || world == null) return;
+            currentSpot = getSpot(player);
+            if (currentSpot < 2) {
+                count = 0;
+                inBounds = false;
+                return;
+            } else {
+                inBounds = true;
+            }
+            Box box = REGIONS[currentSpot - 1];
+            List<Entity> entities = world.getOtherEntities(player, box);
+            int currentCount = 0;
+            for (Entity entity : entities) {
+                if (Detection.isARealPlayer(entity)) {
+                    currentCount++;
+                }
+            }
+
+            count = currentCount;
+        });
+    }
+
+    private static int getSpot(ClientPlayerEntity player) {
+        Vec3d pos = player.getPos();
+        if (HEE2_BOX.contains(pos)) return 2;
+        if (EE2_BOX.contains(pos)) return 2;
+        else if (EE3_BOX.contains(pos)) return 3;
+        else if (CORE_BOX.contains(pos)) return 4;
+        else if (RELIC_BOX.contains(pos) && !Phase.inP5()) return 5;
+        else return 0;
+    }
+
+    public static boolean display() {
+        return Floor7.leapNotifications && inBounds;
+    }
+
+    public static void render(HUDComponent component, DrawContext context) {
+        int x = component.getScaledX();
+        int y = component.getScaledY();
+
+        String start;
+        if (currentSpot == 3) start = (count > 2 ? "§9" : "§4");
+        else start = (count > 3 ? "§9" : "§4");
+
+        RenderUtils.drawCenteredText(context, MinecraftClient.getInstance().textRenderer, Text.literal(start + count + "§9/4 Players Leaped"), x, y, component.getWidth());
+
+    }
+}
diff --git a/src/main/java/blade/addon/features/dungeon/f7/LocationNotifier.java b/src/main/java/blade/addon/features/dungeon/f7/LocationNotifier.java
new file mode 100644
index 0000000..5187488
--- /dev/null
+++ b/src/main/java/blade/addon/features/dungeon/f7/LocationNotifier.java
@@ -0,0 +1,61 @@
+package blade.addon.features.dungeon.f7;
+
+import blade.addon.utils.Location;
+import blade.addon.utils.Scheduler;
+import blade.addon.utils.config.values.Floor7;
+import blade.addon.utils.events.Events;
+import blade.addon.utils.rendering.RenderUtils;
+import config.practical.hud.HUDComponent;
+import net.fabricmc.fabric.api.client.event.lifecycle.v1.ClientTickEvents;
+import net.minecraft.client.MinecraftClient;
+import net.minecraft.client.gui.DrawContext;
+import net.minecraft.sound.SoundEvent;
+import net.minecraft.text.Text;
+import net.minecraft.util.Formatting;
+import net.minecraft.util.Identifier;
+
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+public class LocationNotifier {
+
+    private static final Pattern PATTERN = Pattern.compile("^(At |Inside )");
+
+    private static Text notification = Text.literal("Username is At Location!").formatted(Formatting.GOLD);
+    private static int ticks = 0;
+
+    public static void init() {
+        Events.ON_PARTY_MESSAGE.register((username, message) -> {
+            if (!Floor7.displayLocationNotification || !Location.inDungeon()) return;
+            Matcher matcher = PATTERN.matcher(message);
+            if (!matcher.find()) return;
+            String action = matcher.group(1);
+
+            String screenNotification = (username + " is " + action + message.substring(action.length()) + "!").replaceAll("§.", "");
+            startNotification(screenNotification);
+        });
+
+        ClientTickEvents.END_CLIENT_TICK.register(client -> ticks = Math.max(ticks - 1, 0));
+    }
+
+    public static void startNotification(String message) {
+        notification = Text.literal(message).formatted(Formatting.GOLD);
+        ticks = Floor7.notificationDuration;
+
+        for (int i = 0; i < Floor7.notificationRepetitions; i++) {
+            SoundEvent event = SoundEvent.of(Identifier.of(Floor7.notificationSound));
+            Scheduler.scheduleSound(event, Floor7.notificationVolume, Floor7.notificationPitch, i + 1);
+        }
+    }
+
+    public static boolean display() {
+        return ticks > 0 && Floor7.displayLocationNotification;
+    }
+
+    public static void render(HUDComponent component, DrawContext context) {
+        int x = component.getScaledX();
+        int y = component.getScaledY();
+
+        RenderUtils.drawCenteredText(context, MinecraftClient.getInstance().textRenderer, notification, x, y, component.getWidth());
+    }
+}
diff --git a/src/main/java/blade/addon/features/dungeon/f7/MelodyWarning.java b/src/main/java/blade/addon/features/dungeon/f7/MelodyWarning.java
new file mode 100644
index 0000000..72f57ee
--- /dev/null
+++ b/src/main/java/blade/addon/features/dungeon/f7/MelodyWarning.java
@@ -0,0 +1,84 @@
+package blade.addon.features.dungeon.f7;
+
+import blade.addon.utils.Location;
+import blade.addon.utils.config.values.Floor7;
+import blade.addon.utils.dungeon.DungeonClass;
+import blade.addon.utils.dungeon.Phase;
+import blade.addon.utils.events.Events;
+import blade.addon.utils.rendering.RenderUtils;
+import config.practical.hud.HUDComponent;
+import net.minecraft.client.MinecraftClient;
+import net.minecraft.client.gui.DrawContext;
+import net.minecraft.text.Text;
+
+import java.util.concurrent.CopyOnWriteArrayList;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+public class MelodyWarning {
+
+    private static final Pattern PATTERN = Pattern.compile("(\\d+)%");
+
+    private static final CopyOnWriteArrayList<String> names = new CopyOnWriteArrayList<>();
+
+    private static boolean melodyStarted = false;
+    private static String name;
+    private static int furthestProgress = 0;
+
+    public static void init() {
+        Events.ON_PARTY_MESSAGE.register((username, message) -> {
+            if (!Floor7.notifiyMelody) return;
+            if (!Location.inDungeon() || !Phase.inTerminals()) return;
+
+            Matcher matcher = PATTERN.matcher(message);
+            if (matcher.find()) {
+                int progress = Integer.parseInt(matcher.group(1));
+                if (progress > furthestProgress) {
+                    melodyStarted = true;
+                    name = username;
+                    furthestProgress = progress;
+                    if (!names.contains(username)) {
+                        names.add(username);
+                    }
+                }
+            }
+        });
+
+        Events.ON_TERMINAL.register((username, objective) -> {
+            if (names.contains(username) && objective.equals("terminal")) {
+                reset();
+            }
+        });
+
+        Events.ON_SECTION_CHANGE.register(MelodyWarning::reset);
+
+        Events.ON_LOCATION_CHANGE.register(newLocation -> {
+            if (Location.inDungeon()) {
+                reset();
+            }
+        });
+    }
+
+    private static void reset() {
+        names.clear();
+        furthestProgress = 0;
+        name = "";
+        melodyStarted = false;
+    }
+
+    public static boolean display() {
+        return melodyStarted && Floor7.notifiyMelody;
+    }
+
+    public static void render(HUDComponent component, DrawContext context) {
+        int x = component.getScaledX();
+        int y = component.getScaledY();
+
+        DungeonClass dungeonClass = DungeonClass.getClass(name);
+        int num = Math.min(furthestProgress / 25, 3);
+        Text text = Text.literal("§5§l" + (dungeonClass != null ? dungeonClass.name() : name != null? name: "Someone") + " §r§dhas melody! " + num + "/4");
+
+        RenderUtils.drawCenteredText(context, MinecraftClient.getInstance().textRenderer, text, x, y, component.getWidth());
+    }
+
+}
diff --git a/src/main/java/blade/addon/features/dungeon/f7/PillarExplode.java b/src/main/java/blade/addon/features/dungeon/f7/PillarExplode.java
new file mode 100644
index 0000000..f42aea9
--- /dev/null
+++ b/src/main/java/blade/addon/features/dungeon/f7/PillarExplode.java
@@ -0,0 +1,65 @@
+package blade.addon.features.dungeon.f7;
+
+import blade.addon.utils.Constants;
+import blade.addon.utils.Location;
+import blade.addon.utils.Scheduler;
+import blade.addon.utils.config.values.Floor7;
+import blade.addon.utils.dungeon.Phase;
+import blade.addon.utils.events.Events;
+import blade.addon.utils.rendering.RenderUtils;
+import config.practical.hud.HUDComponent;
+import net.minecraft.client.MinecraftClient;
+import net.minecraft.client.gui.DrawContext;
+import net.minecraft.sound.SoundEvents;
+import net.minecraft.text.Text;
+import net.minecraft.util.Formatting;
+
+public class PillarExplode {
+
+    private static final int TOTAL_TICKS = 20;
+    private static int tick = 0;
+
+    public static void init() {
+        Events.ON_GAME_MESSAGE.register(text -> {
+            if (!Floor7.notifyStormCrush && !Floor7.timePillarExplosion) return;
+            if (!Location.inDungeon() || !Phase.inP2()) return;
+
+            String string = text.getString();
+            if (string == null) return;
+
+            if (string.equals("[BOSS] Storm: Oof") ||string.equals("[BOSS] Storm: Ouch, that hurt!")) {
+                tick = TOTAL_TICKS;
+                Scheduler.scheduleSound(SoundEvents.BLOCK_NOTE_BLOCK_PLING.value(), 1, 1);
+            }
+        });
+
+        Events.ON_SERVER_TICK.register(() -> tick = Math.max(tick - 1, 0));
+    }
+
+    public static boolean displayTimer() {
+        return Floor7.notifyStormCrush && tick > 0;
+    }
+
+    public static void renderTimer(HUDComponent component, DrawContext context) {
+        int x = component.getScaledX();
+        int y = component.getScaledY();
+
+        Formatting formatting = (tick < 6? Formatting.GREEN: Formatting.RED);
+
+        double num = tick * Constants.TICK_DURATION;
+
+        RenderUtils.drawCenteredText(context, MinecraftClient.getInstance().textRenderer, Text.literal(Constants.DECIMAL_FORMAT.format(num).formatted(formatting)), x, y, component.getWidth());
+
+    }
+
+    public static boolean display() {
+        return Floor7.timePillarExplosion && tick > 0;
+    }
+
+    public static void render(HUDComponent component, DrawContext context) {
+        int x = component.getScaledX();
+        int y = component.getScaledY();
+        RenderUtils.drawCenteredText(context, MinecraftClient.getInstance().textRenderer, Text.literal("§6||| §bStorm crushed! §6|||"), x, y, component.getWidth());
+
+    }
+}
diff --git a/src/main/java/blade/addon/features/dungeon/f7/Pre4Notifier.java b/src/main/java/blade/addon/features/dungeon/f7/Pre4Notifier.java
new file mode 100644
index 0000000..b7bfe89
--- /dev/null
+++ b/src/main/java/blade/addon/features/dungeon/f7/Pre4Notifier.java
@@ -0,0 +1,57 @@
+package blade.addon.features.dungeon.f7;
+
+import blade.addon.utils.Scheduler;
+import blade.addon.utils.config.values.Floor7;
+import blade.addon.utils.dungeon.Phase;
+import blade.addon.utils.events.Events;
+import blade.addon.utils.rendering.RenderUtils;
+import config.practical.hud.HUDComponent;
+import net.minecraft.client.MinecraftClient;
+import net.minecraft.client.gui.DrawContext;
+import net.minecraft.client.network.ClientPlayerEntity;
+import net.minecraft.sound.SoundEvents;
+import net.minecraft.text.Text;
+
+public class Pre4Notifier {
+
+    private static final long TOTAL_DURATION = 1500;
+
+    private static long completedTime = 0;
+    private static boolean showNotification = false;
+
+
+    public static void init() {
+        Events.ON_TERMINAL.register((name, objective) -> {
+            if (!Floor7.notifyPre4Completion) return;
+
+            if (objective.equals("device") && atDev()) {
+                completedTime = System.currentTimeMillis();
+                showNotification = true;
+                Scheduler.scheduleSound(SoundEvents.BLOCK_NOTE_BLOCK_PLING.value(), 1, 1);
+            }
+        });
+    }
+
+    private static boolean atDev() {
+        ClientPlayerEntity player = MinecraftClient.getInstance().player;
+        if (player == null) return false;
+        return (player.getX() >= 63 && player.getX() <= 64 && player.getY() == 127 && player.getZ() >= 35 && player.getZ() <= 36);
+    }
+
+    public static boolean disableTitles() {
+        return  Floor7.disableTitlesAtPre4 && atDev() && Phase.inTerminals();
+    }
+
+    public static boolean display() {
+        return showNotification;
+    }
+
+    public static void render(HUDComponent component, DrawContext context) {
+        if (System.currentTimeMillis() - completedTime >= TOTAL_DURATION) showNotification = false;
+
+        int x = component.getScaledX();
+        int y = component.getScaledY();
+
+        RenderUtils.drawCenteredText(context, MinecraftClient.getInstance().textRenderer, Text.literal("§aDevice Completed!"), x, y, component.getWidth());
+    }
+}
diff --git a/src/main/java/blade/addon/features/dungeon/f7/PredevTimer.java b/src/main/java/blade/addon/features/dungeon/f7/PredevTimer.java
new file mode 100644
index 0000000..a507c97
--- /dev/null
+++ b/src/main/java/blade/addon/features/dungeon/f7/PredevTimer.java
@@ -0,0 +1,51 @@
+package blade.addon.features.dungeon.f7;
+
+import blade.addon.utils.Misc;
+import blade.addon.utils.config.values.Floor7;
+import blade.addon.utils.dungeon.DungeonClass;
+import blade.addon.utils.dungeon.Phase;
+import blade.addon.utils.events.Events;
+import blade.addon.utils.times.PersonalBests;
+import net.fabricmc.fabric.api.client.event.lifecycle.v1.ClientTickEvents;
+import net.minecraft.client.network.ClientPlayerEntity;
+import net.minecraft.text.Text;
+
+public class PredevTimer {
+
+    private static long bossEnterTime = 0;
+    private static boolean at3rdDev = false;
+    private static boolean enteredBoss = false;
+
+    public static void init() {
+
+        Events.ON_PHASE_CHANGE.register(() -> {
+            if (Phase.inP1()) {
+                bossEnterTime = System.currentTimeMillis();
+                at3rdDev = false;
+                enteredBoss = true;
+            }
+        });
+
+        Events.ON_LOCATION_CHANGE.register(newLocation -> enteredBoss = false);
+
+        ClientTickEvents.END_CLIENT_TICK.register(client -> {
+            if (!enteredBoss || at3rdDev || (!Floor7.predevForAll && !DungeonClass.isClass(DungeonClass.HEALER))) return;
+            ClientPlayerEntity player = client.player;
+            if (player == null) return;
+            if (Misc.getDistance(player.getX(), player.getZ(), 1, 77) <= 3) {
+                at3rdDev = true;
+            }
+        });
+
+        Events.ON_LEAP.register(message -> {
+            if (at3rdDev && enteredBoss && (DungeonClass.isClass(DungeonClass.HEALER) || Floor7.predevForAll)) {
+                PersonalBests.predevTime.testNewTime(Text.literal("§aPredev completed in "), bossEnterTime);
+                at3rdDev = false;
+                enteredBoss = false;
+            }
+        });
+
+    }
+
+
+}
diff --git a/src/main/java/blade/addon/features/dungeon/f7/RelicTimer.java b/src/main/java/blade/addon/features/dungeon/f7/RelicTimer.java
index d84b1b9..394be5f 100644
--- a/src/main/java/blade/addon/features/dungeon/f7/RelicTimer.java
+++ b/src/main/java/blade/addon/features/dungeon/f7/RelicTimer.java
@@ -8,6 +8,7 @@ import blade.addon.utils.dungeon.Phase;
 import blade.addon.utils.events.Events;
 import blade.addon.utils.rendering.RenderLayers;
 import blade.addon.utils.rendering.RenderUtils;
+import blade.addon.utils.times.PersonalBests;
 import config.practical.hud.HUDComponent;
 import net.fabricmc.fabric.api.client.message.v1.ClientReceiveMessageEvents;
 import net.fabricmc.fabric.api.client.rendering.v1.WorldRenderEvents;
@@ -19,6 +20,10 @@ import net.minecraft.client.render.VertexConsumer;
 import net.minecraft.client.render.VertexConsumerProvider;
 import net.minecraft.client.render.VertexRendering;
 import net.minecraft.client.util.math.MatrixStack;
+import net.minecraft.entity.EquipmentSlot;
+import net.minecraft.entity.decoration.ArmorStandEntity;
+import net.minecraft.item.ItemStack;
+import net.minecraft.text.MutableText;
 import net.minecraft.text.Text;
 import net.minecraft.util.Formatting;
 import net.minecraft.util.math.BlockPos;
@@ -31,18 +36,21 @@ import java.util.regex.Pattern;
 public class RelicTimer {
 
     enum Relic {
-        GREEN(new Box(49, 7, 44, 50, 8, 45), 0xff55ff55),
-        RED(new Box(51, 7, 42, 52, 8, 43), 0xffff5555),
-        PURPLE(new Box(54, 7, 41, 55, 8, 42), 0xffff55ff),
-        ORANGE(new Box(57, 7, 42, 58, 8, 43), 0xffffaa00),
-        BLUE(new Box(59, 7, 44, 60, 8, 45), 0xff55ffff);
+        GREEN(new Box(49, 7, 44, 50, 8, 45), 0xff55ff55, "§cRed"),
+        RED(new Box(51, 7, 42, 52, 8, 43), 0xffff5555, "§aGreen"),
+        PURPLE(new Box(54, 7, 41, 55, 8, 42), 0xffff55ff, "§5Purple"),
+        ORANGE(new Box(57, 7, 42, 58, 8, 43), 0xffffaa00, "§6Orange"),
+        BLUE(new Box(59, 7, 44, 60, 8, 45), 0xff55ffff, "§bBlue");
 
         final Box box;
         final int color;
+        final String name;
+        long placedTime = 0;
 
-        Relic(Box box, int color) {
+        Relic(Box box, int color, String name) {
             this.box = box;
             this.color = color;
+            this.name = name;
         }
     }
 
@@ -51,27 +59,39 @@ public class RelicTimer {
 
     private static final Pattern PATTERN = Pattern.compile("^(.+) picked the Corrupted (Red|Purple|Orange|Green|Blue) Relic!$");
 
-    private static long pickupTime;
+    private static long phaseStartTime;
     private static Relic pickedupRelic = null;
 
     private static int tick = Floor7.relicSpawnTicks;
 
+    //only here to test the relic progress bar
+    private static boolean forceGUI = false;
+
     public static void init() {
         Events.ON_SERVER_TICK.register(() -> {
-            if (!Location.inDungeon() || !Phase.inP5()) return;
-            tick = Math.max(tick - 1, 0);
+            if (!forceGUI) {
+                if (!Location.inDungeon() || !Phase.inP5()) return;
+            }
+            tick = Math.max(tick - 1, -1);
+
+            if (tick == -1) {
+                forceGUI = false;
+            }
         });
 
         Events.ON_LOCATION_CHANGE.register(newLocation -> {
             if (Location.inDungeon()) {
                 tick = Floor7.relicSpawnTicks;
                 pickedupRelic = null;
+                for (Relic relic : Relic.values()) {
+                    relic.placedTime = 0;
+                }
             }
         });
 
         Events.ON_PHASE_CHANGE.register(() -> {
             if (Phase.inP5()) {
-                pickupTime = System.currentTimeMillis();
+                phaseStartTime = System.currentTimeMillis();
             }
         });
 
@@ -92,6 +112,37 @@ public class RelicTimer {
             }
         });
 
+        Events.ON_ENTITY_SPAWNED.register((entity, world) -> {
+            if (!Location.inDungeon() || !Phase.inP5() || !Floor7.showAllRelicTimes) return;
+
+
+            if (entity instanceof ArmorStandEntity armorStand) {
+                ItemStack helmet = armorStand.getEquippedStack(EquipmentSlot.HEAD);
+                Text text = helmet.getName();
+                if (text == null) return;
+                String name = text.getString();
+                if (!name.contains("Relic")) return;
+
+                double ax = armorStand.getX();
+                double az = armorStand.getZ();
+
+                for (Relic relic : Relic.values()) {
+                    if (Misc.getDistance(relic.box.maxX, ax, relic.box.maxZ, az) < 1) {
+                        relic.placedTime = System.currentTimeMillis();
+                        break;
+                    }
+                }
+
+                if (allRelicsPlaced()) {
+                    for (Relic relic : Relic.values()) {
+                        long time = relic.placedTime - phaseStartTime;
+                        Misc.addChatMessage(Text.literal(relic.name + " &aRelic placed in &e" + Constants.DECIMAL_FORMAT.format(time) + "s&a."));
+                    }
+                }
+            }
+
+        });
+
         Events.ON_BLOCK_INTERACTION.register((result, itemStack) -> {
             if (!Location.inDungeon() || !Phase.inP5() || pickedupRelic == null) return false;
             BlockPos pos = result.getBlockPos();
@@ -102,11 +153,18 @@ public class RelicTimer {
 
             if (pos.getX() == pickedupRelic.box.minX && (pos.getY() == pickedupRelic.box.minY || pos.getY() == pickedupRelic.box.minY - 1) && pos.getZ() == pickedupRelic.box.minZ) {
                 if (Floor7.enableRelicPlaceTime) {
-                    double diff = (System.currentTimeMillis() - pickupTime) / 1000.0;
-                    Misc.addChatMessage(Text.literal("The ").formatted(Formatting.GREEN)
+                    MutableText text = Text.literal("The ").formatted(Formatting.GREEN)
                             .append(Text.literal(pickedupRelic.name().toLowerCase()).withColor(pickedupRelic.color))
-                            .append(" relic was placed in ").formatted(Formatting.GREEN)
-                            .append(Text.literal(Constants.DECIMAL_FORMAT.format(diff) + "s.").formatted(Formatting.YELLOW)));
+                            .append(" relic was placed in ").formatted(Formatting.GREEN);
+
+                    switch (pickedupRelic) {
+                        case RED -> PersonalBests.redRelicTime.testNewTime(text, phaseStartTime);
+                        case ORANGE -> PersonalBests.orangeRelicTime.testNewTime(text, phaseStartTime);
+                        case BLUE -> PersonalBests.blueRelicTime.testNewTime(text, phaseStartTime);
+                        case GREEN -> PersonalBests.greenRelicTime.testNewTime(text, phaseStartTime);
+                        case PURPLE -> PersonalBests.purpleRelicTime.testNewTime(text, phaseStartTime);
+                    }
+
                 }
                 pickedupRelic = null;
             } else {
@@ -144,8 +202,20 @@ public class RelicTimer {
 
     }
 
+    public static void testRelicGUI() {
+        forceGUI = true;
+        tick = Floor7.relicSpawnTicks;
+    }
+
+    private static boolean allRelicsPlaced() {
+        for (Relic relic : Relic.values()) {
+            if (relic.placedTime == 0) return false;
+        }
+        return true;
+    }
+
     public static boolean display() {
-        return Floor7.enableRelicStartTimer && Location.inDungeon() && Phase.inP5() && tick > 0;
+        return Floor7.enableRelicStartTimer && Location.inDungeon() && Phase.inP5() && tick > -1 && !Floor7.replaceWithProgressBar;
     }
 
     public static void render(HUDComponent component, DrawContext context) {
@@ -159,4 +229,29 @@ public class RelicTimer {
         RenderUtils.drawCenteredText(context, MinecraftClient.getInstance().textRenderer, Text.literal(Constants.DECIMAL_FORMAT.format(num)), x, y, component.getWidth(), color);
 
     }
+
+    public static boolean displayProgressBar() {
+        if (forceGUI) return true;
+        return Floor7.enableRelicStartTimer && Location.inDungeon() && Phase.inP5() && tick > -1 && Floor7.replaceWithProgressBar;
+    }
+
+    public static void renderProgressBar(HUDComponent component, DrawContext context) {
+        int x = component.getScaledX();
+        int y = component.getScaledY();
+
+        //from valleyAddons
+        StringBuilder message = new StringBuilder("§8[");
+
+        int diff = Floor7.relicSpawnTicks - tick;
+        for (int i = 0; i < Floor7.relicSpawnTicks; i++) {
+            if (i < diff) {
+                if (tick < 2) message.append("§a|");
+                else message.append("§c|");
+            } else message.append("§7|");
+        }
+        message.append("§8]");
+
+        RenderUtils.drawCenteredText(context, MinecraftClient.getInstance().textRenderer, Text.literal(message.toString()), x, y, component.getWidth(), 0xffffffff);
+
+    }
 }
diff --git a/src/main/java/blade/addon/features/dungeon/MobHighlight.java b/src/main/java/blade/addon/features/highlight/MobHighlight.java
similarity index 99%
rename from src/main/java/blade/addon/features/dungeon/MobHighlight.java
rename to src/main/java/blade/addon/features/highlight/MobHighlight.java
index 90534c2..a8c4d72 100644
--- a/src/main/java/blade/addon/features/dungeon/MobHighlight.java
+++ b/src/main/java/blade/addon/features/highlight/MobHighlight.java
@@ -1,4 +1,4 @@
-package blade.addon.features.dungeon;
+package blade.addon.features.highlight;
 
 import blade.addon.utils.Location;
 import blade.addon.utils.Misc;
@@ -337,8 +337,8 @@ public class MobHighlight {
 
     private static boolean isMiniBoss(Entity entity) {
         if (entity instanceof PlayerEntity player) {
-            ItemStack leggings = player.getInventory().getStack(39);
-            int id = Item.getRawId(leggings.getItem());
+            ItemStack helmet = player.getInventory().getStack(39);
+            int id = Item.getRawId(helmet.getItem());
             return id == DIAMOND_HELMET_ID || id == PLAYER_HEAD_ID;
         }
 
diff --git a/src/main/java/blade/addon/features/item/ItemRarity.java b/src/main/java/blade/addon/features/item/ItemRarity.java
index ed88c07..4012d54 100644
--- a/src/main/java/blade/addon/features/item/ItemRarity.java
+++ b/src/main/java/blade/addon/features/item/ItemRarity.java
@@ -2,7 +2,7 @@ package blade.addon.features.item;
 
 public enum ItemRarity {
     NONE(0x0),
-    COMMON(0x88ffffff),
+    COMMON(0x99dddddd),
     UNCOMMON(0x8842ab42),
     RARE(0x884c4cd0),
     EPIC(0x88671067),
@@ -14,7 +14,7 @@ public enum ItemRarity {
     ULTIMATE(0x88a10202),
     ADMIN(0x88aa0000);
 
-    private int color;
+    private final int color;
 
     public int getColor() {
         return this.color;
diff --git a/src/main/java/blade/addon/features/item/ItemRarityHighlight.java b/src/main/java/blade/addon/features/item/ItemRarityHighlight.java
index ea21293..044fb48 100644
--- a/src/main/java/blade/addon/features/item/ItemRarityHighlight.java
+++ b/src/main/java/blade/addon/features/item/ItemRarityHighlight.java
@@ -31,18 +31,18 @@ public class ItemRarityHighlight {
 
         List<Text> lines = lore.lines();
         if (lines.isEmpty()) return ItemRarity.NONE;
-        Text line = lines.getLast();
 
-        if (line == null) return ItemRarity.NONE;
-        String string = line.getString();
-        String[] rarityStrings = string.split(" ");
+        for (Text line: lines.reversed()) {
+            String string = line.getString();
+            String[] rarityStrings = string.split(" ");
 
-        for (String testString: rarityStrings) {
-            try {
-                ItemRarity rarity = ItemRarity.valueOf(testString);
-                return rarity;
-            } catch (IllegalArgumentException ignored) {
+            for (String testString: rarityStrings) {
+                try {
+                    ItemRarity rarity = ItemRarity.valueOf(testString);
+                    return rarity;
+                } catch (IllegalArgumentException ignored) {
 
+                }
             }
         }
         return ItemRarity.NONE;
diff --git a/src/main/java/blade/addon/features/item/ProtectItem.java b/src/main/java/blade/addon/features/item/ProtectItem.java
new file mode 100644
index 0000000..3f886ae
--- /dev/null
+++ b/src/main/java/blade/addon/features/item/ProtectItem.java
@@ -0,0 +1,150 @@
+package blade.addon.features.item;
+
+import blade.addon.utils.Constants;
+import blade.addon.utils.Debug;
+import blade.addon.utils.Location;
+import blade.addon.utils.Misc;
+import blade.addon.utils.config.FolderUtility;
+import blade.addon.utils.dungeon.Phase;
+import config.practical.manager.ConfigManager;
+import config.practical.manager.ConfigValue;
+import net.minecraft.client.MinecraftClient;
+import net.minecraft.client.gl.RenderPipelines;
+import net.minecraft.client.gui.DrawContext;
+import net.minecraft.client.gui.screen.ingame.HandledScreen;
+import net.minecraft.client.network.ClientPlayerEntity;
+import net.minecraft.component.DataComponentTypes;
+import net.minecraft.component.type.NbtComponent;
+import net.minecraft.item.ItemStack;
+import net.minecraft.nbt.NbtCompound;
+import net.minecraft.screen.ScreenHandler;
+import net.minecraft.screen.slot.Slot;
+import net.minecraft.text.Text;
+import net.minecraft.util.Identifier;
+import net.minecraft.util.collection.DefaultedList;
+
+import java.util.HashSet;
+import java.util.List;
+
+public class ProtectItem {
+
+    private static final Identifier SPRITE = Identifier.of(Constants.NAMESPACE, "lock");
+
+    public static final ConfigManager itemManager = new ConfigManager(FolderUtility.OLD_PATH + FolderUtility.PROTECT_ITEMS_NAME,
+            List.of(ProtectItem.class));
+
+    @ConfigValue
+    public static HashSet<String> protectedItems = new HashSet<>();
+
+    public static void protectSelected() {
+        ClientPlayerEntity player = MinecraftClient.getInstance().player;
+        if (player == null) {
+            Debug.LOGGER.warn("Cant find client player to protect item");
+            return;
+        }
+
+        ItemStack item = player.getInventory().getSelectedStack();
+        String uuid = getUuid(item);
+        if (uuid == null) {
+            Misc.addChatMessage(Text.literal("Cant protect this item, it does not have a uuid"));
+            return;
+        }
+        ProtectedItemHolder holder = (ProtectedItemHolder) (Object) item;
+        if (protectedItems.contains(uuid)) {
+            protectedItems.remove(uuid);
+            holder.blade_addons$setProtected(false);
+            Misc.addChatMessage(Text.literal("Item ").append(item.getName()).append(" is NOT protected anymore"));
+        } else {
+            protectedItems.add(uuid);
+            holder.blade_addons$setProtected(true);
+            Misc.addChatMessage(Text.literal("Item ").append(item.getName()).append(" is protected"));
+        }
+
+        itemManager.save();
+    }
+
+    public static boolean protect(ItemStack stack) {
+
+        String uuid = getUuid(stack);
+
+        //TODO: add smth for non uuid items
+        if (uuid == null) {
+            return false;
+        }
+
+        return protectedItems.contains(uuid);
+    }
+
+    public static String getUuid(ItemStack item) {
+        NbtComponent nbt = item.get(DataComponentTypes.CUSTOM_DATA);
+        if (nbt == null) return null;
+
+        NbtCompound compound = nbt.copyNbt();
+
+        return compound.getString("uuid", null);
+    }
+
+    public static void draw(DrawContext context, ItemStack stack, int x, int y) {
+        ProtectedItemHolder holder = (ProtectedItemHolder) (Object) stack;
+        assert holder != null;
+
+        if (!holder.blade_addons$hasScannedProtection()) {
+            String uuid = getUuid(stack);
+            if (uuid == null) return;
+            holder.blade_addons$setProtected(protectedItems.contains(uuid));
+        }
+
+        if (!holder.blade_addons$isProtected()) return;
+
+        context.drawGuiTexture(RenderPipelines.GUI_TEXTURED, SPRITE, x, y, 16, 16, 0x99ffffff);
+    }
+
+    public static boolean blockGUI(HandledScreen screen, ItemStack item) {
+        if (!protect(item)) return false;
+
+
+        Text title = screen.getTitle();
+        if (title == null) return false;
+        String name = title.getString();
+
+        if (name.contains("Auction")) {
+            Misc.addChatMessage(Text.literal("Protected ").append(item.getName()).append(" From being auctioned"));
+            return true;
+        }
+
+        if (name.contains("Salvage")) {
+            Misc.addChatMessage(Text.literal("Protected ").append(item.getName()).append(" From being salvaged"));
+            return true;
+        }
+
+        ScreenHandler handler = screen.getScreenHandler();
+        DefaultedList<Slot> slots = handler.slots;
+
+        if (slots.size() > 49) {
+            Slot slot = slots.get(49);
+            ItemStack stack = slot.getStack();
+            Text itemName = stack.getName();
+            if (itemName != null && itemName.getString().contains("Sell Item")) {
+                Misc.addChatMessage(Text.literal("Protected ").append(item.getName()).append(" From being sold"));
+                return true;
+            }
+
+            slot = slots.get(4);
+            stack = slot.getStack();
+            itemName = stack.getName();
+            if (itemName != null && itemName.getString().contains("⇦ Your stuff")) {
+                Misc.addChatMessage(Text.literal("Protected ").append(item.getName()).append(" From being traded"));
+                return true;
+            }
+
+        }
+
+        System.out.println(slots.size());
+
+        return false;
+    }
+
+    public static boolean isInADungeon() {
+        return Location.inDungeon() && Phase.runStarted() && !Phase.runOver() && Location.hasRecivedLocation();
+    }
+}
diff --git a/src/main/java/blade/addon/features/item/ProtectedItemHolder.java b/src/main/java/blade/addon/features/item/ProtectedItemHolder.java
new file mode 100644
index 0000000..44ff4f0
--- /dev/null
+++ b/src/main/java/blade/addon/features/item/ProtectedItemHolder.java
@@ -0,0 +1,14 @@
+package blade.addon.features.item;
+
+public interface ProtectedItemHolder {
+
+    boolean scannedProtected = false;
+
+    boolean isItemProtected = false;
+
+    boolean blade_addons$hasScannedProtection();
+
+    void blade_addons$setProtected(boolean value);
+
+    boolean blade_addons$isProtected();
+}
diff --git a/src/main/java/blade/addon/features/item/StarCountHighlight.java b/src/main/java/blade/addon/features/item/StarCountHighlight.java
new file mode 100644
index 0000000..a9c6a4a
--- /dev/null
+++ b/src/main/java/blade/addon/features/item/StarCountHighlight.java
@@ -0,0 +1,45 @@
+package blade.addon.features.item;
+
+import net.minecraft.client.MinecraftClient;
+import net.minecraft.client.font.TextRenderer;
+import net.minecraft.client.gui.DrawContext;
+import net.minecraft.component.DataComponentTypes;
+import net.minecraft.component.type.NbtComponent;
+import net.minecraft.item.ItemStack;
+import net.minecraft.nbt.NbtCompound;
+
+public class StarCountHighlight {
+
+    public static void draw(DrawContext context, ItemStack stack, int x, int y) {
+        StarCountHolder holder = (StarCountHolder) (Object) stack;
+        assert holder != null;
+
+        if (!holder.blade_addons$hasScannedStars()) {
+            holder.blade_addons$setStarCount(getStarCount(stack));
+        }
+
+        int starCount = holder.blade_addons$getStarCount();
+        if (starCount == 0) return;
+
+
+        TextRenderer textRenderer = MinecraftClient.getInstance().textRenderer;
+        String starText = "" + starCount;
+        context.drawText(MinecraftClient.getInstance().textRenderer, starText, x + 17 - textRenderer.getWidth(starText), y + 18 - textRenderer.fontHeight, 0xffffffff, true);
+    }
+
+    public static int getStarCount(ItemStack item) {
+        NbtComponent nbt = item.get(DataComponentTypes.CUSTOM_DATA);
+        if (nbt == null)  return 0;
+
+        NbtCompound compound = nbt.copyNbt();
+
+        int starCount = compound.getInt("upgrade_level", 0);
+        if (starCount == 0) {
+            //the old star system they used to use.
+            // Some items don't have the new data so this is a fallback
+            starCount = compound.getInt("dungeon_item_level", 0);
+        }
+
+        return starCount;
+    }
+}
diff --git a/src/main/java/blade/addon/features/item/StarCountHolder.java b/src/main/java/blade/addon/features/item/StarCountHolder.java
new file mode 100644
index 0000000..d9db5ae
--- /dev/null
+++ b/src/main/java/blade/addon/features/item/StarCountHolder.java
@@ -0,0 +1,15 @@
+package blade.addon.features.item;
+
+public interface StarCountHolder {
+
+    boolean scannedStars = false;
+
+    int starCount = 0;
+
+    boolean blade_addons$hasScannedStars();
+
+    void blade_addons$setStarCount(int count);
+
+    int blade_addons$getStarCount();
+}
+
diff --git a/src/main/java/blade/addon/features/other/KickedTimer.java b/src/main/java/blade/addon/features/other/KickedTimer.java
new file mode 100644
index 0000000..a2dc371
--- /dev/null
+++ b/src/main/java/blade/addon/features/other/KickedTimer.java
@@ -0,0 +1,45 @@
+package blade.addon.features.other;
+
+import blade.addon.utils.Constants;
+import blade.addon.utils.config.values.ExtraOptions;
+import blade.addon.utils.events.Events;
+import blade.addon.utils.rendering.RenderUtils;
+import config.practical.hud.HUDComponent;
+import net.minecraft.client.MinecraftClient;
+import net.minecraft.client.gui.DrawContext;
+
+public class KickedTimer {
+
+    private static boolean isKicked = false;
+    private static long kickedTime = 0;
+
+    public static void init() {
+        Events.ON_GAME_MESSAGE.register(text -> {
+            if (!ExtraOptions.enableKickedTimer) return;
+            String string = text.getString();
+            if (string == null) return;
+
+            if (string.contains("You were kicked while joining that server!")) {
+                kickedTime = System.currentTimeMillis();
+                isKicked = true;
+
+            }
+        });
+    }
+
+    public static boolean display() {
+        return isKicked;
+    }
+
+    public static void render(HUDComponent component, DrawContext context) {
+        long diff = System.currentTimeMillis() - kickedTime;
+        if (diff > 60 * 1000) isKicked = false;
+
+        int x = component.getScaledX();
+        int y = component.getScaledY();
+
+        RenderUtils.drawCenteredText(context, MinecraftClient.getInstance().textRenderer, "§5Time kicked: §f" + Constants.DECIMAL_FORMAT.format(Math.min(diff / 1000.0, 60)) + "s", x, y, component.getWidth());
+
+    }
+
+}
diff --git a/src/main/java/blade/addon/features/other/PracticeSS.java b/src/main/java/blade/addon/features/other/PracticeSS.java
new file mode 100644
index 0000000..4b1cc0a
--- /dev/null
+++ b/src/main/java/blade/addon/features/other/PracticeSS.java
@@ -0,0 +1,390 @@
+package blade.addon.features.other;
+
+import blade.addon.utils.Constants;
+import blade.addon.utils.Location;
+import blade.addon.utils.Misc;
+import blade.addon.utils.config.values.ExtraOptions;
+import blade.addon.utils.events.Events;
+import blade.addon.utils.rendering.RenderLayers;
+import blade.addon.utils.rendering.RenderUtils;
+import blade.addon.utils.times.PersonalBests;
+import net.fabricmc.fabric.api.client.event.lifecycle.v1.ClientTickEvents;
+import net.fabricmc.fabric.api.client.rendering.v1.WorldRenderContext;
+import net.fabricmc.fabric.api.client.rendering.v1.WorldRenderEvents;
+import net.minecraft.block.Block;
+import net.minecraft.block.BlockState;
+import net.minecraft.block.Blocks;
+import net.minecraft.client.MinecraftClient;
+import net.minecraft.client.render.Camera;
+import net.minecraft.client.render.VertexConsumer;
+import net.minecraft.client.render.VertexConsumerProvider;
+import net.minecraft.client.render.VertexRendering;
+import net.minecraft.client.util.math.MatrixStack;
+import net.minecraft.client.world.ClientWorld;
+import net.minecraft.item.ItemStack;
+import net.minecraft.state.property.Properties;
+import net.minecraft.text.Text;
+import net.minecraft.util.hit.BlockHitResult;
+import net.minecraft.util.math.BlockPos;
+import net.minecraft.util.math.Box;
+import net.minecraft.util.math.Direction;
+import net.minecraft.util.math.Vec3d;
+
+import java.util.Collections;
+import java.util.List;
+import java.util.concurrent.CopyOnWriteArrayList;
+
+public class PracticeSS {
+
+    private static final Box NORTH_BOX = new Box(5 / 16.0, 6 / 16.0, 14 / 16.0, 11 / 16.0, 10 / 16.0, 1);
+    private static final Box SOUTH_BOX = new Box(5 / 16.0, 6 / 16.0, 0, 11 / 16.0, 10 / 16.0, 2 / 16.0);
+    private static final Box EAST_BOX = new Box(0, 6 / 16.0, 5 / 16.0, 2 / 16.0, 10 / 16.0, 11 / 16.0);
+    private static final Box WEST_BOX = new Box(14 / 16.0, 6 / 16.0, 5 / 16.0, 1, 10 / 16.0, 11 / 16.0);
+
+    private static final List<BlockPos> START_POSES = List.of(new BlockPos(-17, 5, -26), new BlockPos(-37, 5, -26), new BlockPos(-51, 5, -26), new BlockPos(-74, 5, -26), new BlockPos(7, 5, -26), new BlockPos(-60, 5, -26), new BlockPos(-6, 5, -26), new BlockPos(-28, 5, -26));
+    private static final CopyOnWriteArrayList<BlockPos> buttons = new CopyOnWriteArrayList<>();
+
+    private static BlockPos startPos = null;
+
+    private static final long SKIP_TIME = 500;
+    private static final int LAST_SHOWN_DURATION = 11;
+    private static final int UNLUCKY_DURATION = 35;
+
+    private static boolean started = false;
+    private static boolean showingPattern = false;
+
+    private static int currentIndex = 0;
+    private static int endIndex = 4;
+
+    private static int ticksLeft = 0;
+
+    private static long startTime = 0;
+
+    private static boolean inSkipPhase = false;
+    private static int clicks = 0;
+    private static long skippedTime = 0;
+    private static boolean skipped = false;
+
+    private static boolean wasLuckyButton = false;
+    private static int ticksSinceLuckyButton = 0;
+
+    private static Direction direction = null;
+
+    public static void init() {
+
+        Events.ON_BLOCK_INTERACTION.register((BlockHitResult result, ItemStack item) -> {
+            if (!ExtraOptions.practiceSSAnywhere && !Location.in(Location.PRIVATE_ISLAND)) return false;
+            ClientWorld world = MinecraftClient.getInstance().world;
+            if (world == null) return false;
+            BlockPos pos = result.getBlockPos();
+            BlockState state = world.getBlockState(pos);
+            Block block = state.getBlock();
+
+            if (
…… diff 过长已截断 ……
- added color option for storm ticktimer - added hide status effects overlay - changed width and color of termsplits - added a combine ticktimers and combine notifications - added block incorrect relic placement - added drag spawn timer - added disable glowing - added chat prefix - added selected pet in menu and as a hud element - fixed hide after leap hiding mobs that are player entitiesGabriel Ellnebrand · 2025-12-15 · e8e627b
查看变更
gradle.properties                                                                       |   4 +-
 src/main/java/blade/addon/Bladeaddons.java                                              |   6 ++-
 src/main/java/blade/addon/features/dungeon/HidePlayers.java                             |   3 ++
 src/main/java/blade/addon/features/dungeon/f7/BossWaypoints.java                        |   8 +---
 src/main/java/blade/addon/features/dungeon/f7/CrystalSpawn.java                         |   7 ++-
 src/main/java/blade/addon/features/dungeon/f7/DragSpawnTimer.java                       | 167 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 src/main/java/blade/addon/features/dungeon/f7/InvincibilityTimer.java                   |  23 ++-------
 src/main/java/blade/addon/features/dungeon/f7/RelicTimer.java                           |  64 ++++++++++---------------
 src/main/java/blade/addon/features/dungeon/f7/StormTickTimer.java                       |  15 +++---
 src/main/java/blade/addon/features/item/ItemRarityDrawer.java                           |  23 ---------
 src/main/java/blade/addon/features/item/ItemRarityHighlight.java                        |  50 +++++++++++++++++++
 src/main/java/blade/addon/features/item/PetHolder.java                                  |  14 ++++++
 src/main/java/blade/addon/features/item/SelectedPetHighlight.java                       |  39 +++++++++++++++
 src/main/java/blade/addon/features/{ => other}/DianaNotifier.java                       |   7 ++-
 src/main/java/blade/addon/features/other/SelectedPet.java                               | 159 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 src/main/java/blade/addon/mixin/EntityMixin.java                                        |  19 ++++++++
 src/main/java/blade/addon/mixin/HandledScreenMixin.java                                 |  25 ++++++++--
 src/main/java/blade/addon/mixin/InGameHudMixin.java                                     |  11 ++++-
 src/main/java/blade/addon/mixin/ItemStackMixin.java                                     |  25 +++++++++-
 src/main/java/blade/addon/mixin/MinecraftClientMixin.java                               |  34 +++++++++++++
 src/main/java/blade/addon/mixin/NetworkMixin.java                                       |   8 ++++
 src/main/java/blade/addon/mixin/ServerTickMixin.java                                    |  12 +++++
 src/main/java/blade/addon/mixin/StatusEffectsDisplayMixin.java                          |  27 +++++++++++
 src/main/java/blade/addon/utils/Debug.java                                              |  17 +++++--
 src/main/java/blade/addon/utils/JsonUtility.java                                        |   4 +-
 src/main/java/blade/addon/utils/Keybinds.java                                           |   5 +-
 src/main/java/blade/addon/utils/Misc.java                                               |  48 +++++++------------
 src/main/java/blade/addon/utils/Scheduler.java                                          |  21 +++-----
 src/main/java/blade/addon/utils/config/Config.java                                      |  46 ++++++++++++------
 src/main/java/blade/addon/utils/config/components/CombinedScreenNotifications.java      |  37 ++++++++++++++
 src/main/java/blade/addon/utils/config/components/CombinedTickTimer.java                |  53 ++++++++++++++++++++
 src/main/java/blade/addon/utils/config/components/Components.java                       |  32 ++++++++++---
 src/main/java/blade/addon/utils/config/values/Dungeons.java                             |   3 ++
 src/main/java/blade/addon/utils/config/values/ExtraOptions.java                         |  18 +++++++
 src/main/java/blade/addon/utils/config/values/Floor7.java                               |  19 ++++++++
 src/main/java/blade/addon/utils/dungeon/DungeonClass.java                               |   9 ++++
 src/main/java/blade/addon/utils/dungeon/Phase.java                                      |   9 ++--
 src/main/java/blade/addon/utils/dungeon/Section.java                                    |  16 ++++---
 src/main/java/blade/addon/utils/dungeon/Split.java                                      |   6 +--
 src/main/java/blade/addon/utils/events/CancelableEvent.java                             |   6 +++
 src/main/java/blade/addon/utils/events/CancelableEventHandler.java                      |  22 +++++++++
 src/main/java/blade/addon/utils/events/Events.java                                      |   8 ++++
 src/main/java/blade/addon/utils/events/interfaces/BlockInteractionEvent.java            |  10 ++++
 src/main/java/blade/addon/utils/events/interfaces/ParticleEvent.java                    |   8 ++++
 src/main/java/blade/addon/utils/events/interfaces/PetEvent.java                         |   5 ++
 src/main/java/blade/addon/utils/rendering/RenderUtils.java                              |   4 ++
 src/main/resources/assets/blade-addons/Blade-addons-picture.png                         | Bin 0 -> 128048 bytes
 src/main/resources/assets/blade-addons/textures/gui/sprites/pets/ammonite.png           | Bin 0 -> 2186 bytes
 src/main/resources/assets/blade-addons/textures/gui/sprites/pets/ankylosaurus.png       | Bin 0 -> 1963 bytes
 src/main/resources/assets/blade-addons/textures/gui/sprites/pets/armadillo.png          | Bin 0 -> 2150 bytes
 src/main/resources/assets/blade-addons/textures/gui/sprites/pets/baby-yeti.png          | Bin 0 -> 2828 bytes
 src/main/resources/assets/blade-addons/textures/gui/sprites/pets/bal.png                | Bin 0 -> 2081 bytes
 src/main/resources/assets/blade-addons/textures/gui/sprites/pets/bat.png                | Bin 0 -> 1811 bytes
 src/main/resources/assets/blade-addons/textures/gui/sprites/pets/bee.png                | Bin 0 -> 1294 bytes
 src/main/resources/assets/blade-addons/textures/gui/sprites/pets/bingo.png              | Bin 0 -> 2216 bytes
 src/main/resources/assets/blade-addons/textures/gui/sprites/pets/black-cat.png          | Bin 0 -> 2195 bytes
 src/main/resources/assets/blade-addons/textures/gui/sprites/pets/blaze.png              | Bin 0 -> 2261 bytes
 src/main/resources/assets/blade-addons/textures/gui/sprites/pets/blue-whale.png         | Bin 0 -> 1596 bytes
 src/main/resources/assets/blade-addons/textures/gui/sprites/pets/chicken.png            | Bin 0 -> 2978 bytes
 src/main/resources/assets/blade-addons/textures/gui/sprites/pets/dolphin.png            | Bin 0 -> 982 bytes
 src/main/resources/assets/blade-addons/textures/gui/sprites/pets/eerie.png              | Bin 0 -> 4595 bytes
 src/main/resources/assets/blade-addons/textures/gui/sprites/pets/elephant.png           | Bin 0 -> 2483 bytes
 src/main/resources/assets/blade-addons/textures/gui/sprites/pets/ender-dragon.png       | Bin 0 -> 2433 bytes
 src/main/resources/assets/blade-addons/textures/gui/sprites/pets/enderman.png           | Bin 0 -> 1233 bytes
 src/main/resources/assets/blade-addons/textures/gui/sprites/pets/endermite.png          | Bin 0 -> 1942 bytes
 src/main/resources/assets/blade-addons/textures/gui/sprites/pets/flying-fish-mythic.png | Bin 0 -> 2278 bytes
 src/main/resources/assets/blade-addons/textures/gui/sprites/pets/frog.png               | Bin 0 -> 2334 bytes
 src/main/resources/assets/blade-addons/textures/gui/sprites/pets/ghoul.png              | Bin 0 -> 2901 bytes
 src/main/resources/assets/blade-addons/textures/gui/sprites/pets/giraffe.png            | Bin 0 -> 1954 bytes
 src/main/resources/assets/blade-addons/textures/gui/sprites/pets/glacite-golem.png      | Bin 0 -> 1875 bytes
 src/main/resources/assets/blade-addons/textures/gui/sprites/pets/goblin.png             | Bin 0 -> 2060 bytes
 src/main/resources/assets/blade-addons/textures/gui/sprites/pets/golden-dragon.png      | Bin 0 -> 2196 bytes
 src/main/resources/assets/blade-addons/textures/gui/sprites/pets/golem.png              | Bin 0 -> 2383 bytes
 src/main/resources/assets/blade-addons/textures/gui/sprites/pets/grandma-wolf.png       | Bin 0 -> 2288 bytes
 src/main/resources/assets/blade-addons/textures/gui/sprites/pets/griffin.png            | Bin 0 -> 3047 bytes
 src/main/resources/assets/blade-addons/textures/gui/sprites/pets/guardian.png           | Bin 0 -> 3303 bytes
 src/main/resources/assets/blade-addons/textures/gui/sprites/pets/hedgehog.png           | Bin 0 -> 2261 bytes
 src/main/resources/assets/blade-addons/textures/gui/sprites/pets/hermit-crab.png        | Bin 0 -> 1902 bytes
 src/main/resources/assets/blade-addons/textures/gui/sprites/pets/horse.png              | Bin 0 -> 1948 bytes
 src/main/resources/assets/blade-addons/textures/gui/sprites/pets/hound.png              | Bin 0 -> 2085 bytes
 src/main/resources/assets/blade-addons/textures/gui/sprites/pets/jade-dragon.png        | Bin 0 -> 1927 bytes
 src/main/resources/assets/blade-addons/textures/gui/sprites/pets/jellyfish.png          | Bin 0 -> 2762 bytes
 src/main/resources/assets/blade-addons/textures/gui/sprites/pets/jerry.png              | Bin 0 -> 1187 bytes
 src/main/resources/assets/blade-addons/textures/gui/sprites/pets/kuudra.png             | Bin 0 -> 2412 bytes
 src/main/resources/assets/blade-addons/textures/gui/sprites/pets/lion.png               | Bin 0 -> 2602 bytes
 src/main/resources/assets/blade-addons/textures/gui/sprites/pets/magma-cube.png         | Bin 0 -> 1751 bytes
 src/main/resources/assets/blade-addons/textures/gui/sprites/pets/mammoth.png            | Bin 0 -> 2020 bytes
 src/main/resources/assets/blade-addons/textures/gui/sprites/pets/megalodon.png          | Bin 0 -> 1715 bytes
 src/main/resources/assets/blade-addons/textures/gui/sprites/pets/mithril-golem.png      | Bin 0 -> 2526 bytes
 src/main/resources/assets/blade-addons/textures/gui/sprites/pets/mole.png               | Bin 0 -> 1706 bytes
 src/main/resources/assets/blade-addons/textures/gui/sprites/pets/monkey.png             | Bin 0 -> 2333 bytes
 src/main/resources/assets/blade-addons/textures/gui/sprites/pets/mooshroom-cow.png      | Bin 0 -> 1486 bytes
 src/main/resources/assets/blade-addons/textures/gui/sprites/pets/mosquito.png           | Bin 0 -> 3047 bytes
 src/main/resources/assets/blade-addons/textures/gui/sprites/pets/ocelot.png             | Bin 0 -> 2545 bytes
 src/main/resources/assets/blade-addons/textures/gui/sprites/pets/owl.png                | Bin 0 -> 1719 bytes
 src/main/resources/assets/blade-addons/textures/gui/sprites/pets/parrot.png             | Bin 0 -> 1969 bytes
 src/main/resources/assets/blade-addons/textures/gui/sprites/pets/penguin.png            | Bin 0 -> 1429 bytes
 src/main/resources/assets/blade-addons/textures/gui/sprites/pets/phoenix.png            | Bin 0 -> 2721 bytes
 src/main/resources/assets/blade-addons/textures/gui/sprites/pets/pig.png                | Bin 0 -> 2606 bytes
 src/main/resources/assets/blade-addons/textures/gui/sprites/pets/pigman.png             | Bin 0 -> 2906 bytes
 src/main/resources/assets/blade-addons/textures/gui/sprites/pets/rabbit.png             | Bin 0 -> 1570 bytes
 src/main/resources/assets/blade-addons/textures/gui/sprites/pets/rat.png                | Bin 0 -> 2144 bytes
 src/main/resources/assets/blade-addons/textures/gui/sprites/pets/reindeer.png           | Bin 0 -> 1864 bytes
 src/main/resources/assets/blade-addons/textures/gui/sprites/pets/rift-ferret.png        | Bin 0 -> 1596 bytes
 src/main/resources/assets/blade-addons/textures/gui/sprites/pets/rock.png               | Bin 0 -> 2648 bytes
 src/main/resources/assets/blade-addons/textures/gui/sprites/pets/scatha.png             | Bin 0 -> 2116 bytes
 src/main/resources/assets/blade-addons/textures/gui/sprites/pets/sheep.png              | Bin 0 -> 2685 bytes
 src/main/resources/assets/blade-addons/textures/gui/sprites/pets/silverfish.png         | Bin 0 -> 2157 bytes
 src/main/resources/assets/blade-addons/textures/gui/sprites/pets/skeleton-horse.png     | Bin 0 -> 1833 bytes
 src/main/resources/assets/blade-addons/textures/gui/sprites/pets/skeleton.png           | Bin 0 -> 1563 bytes
 src/main/resources/assets/blade-addons/textures/gui/sprites/pets/slug.png               | Bin 0 -> 1712 bytes
 src/main/resources/assets/blade-addons/textures/gui/sprites/pets/snail.png              | Bin 0 -> 2088 bytes
 src/main/resources/assets/blade-addons/textures/gui/sprites/pets/snowman.png            | Bin 0 -> 1694 bytes
 src/main/resources/assets/blade-addons/textures/gui/sprites/pets/spider.png             | Bin 0 -> 2354 bytes
 src/main/resources/assets/blade-addons/textures/gui/sprites/pets/spinosaurus.png        | Bin 0 -> 1990 bytes
 src/main/resources/assets/blade-addons/textures/gui/sprites/pets/spirit.png             | Bin 0 -> 2199 bytes
 src/main/resources/assets/blade-addons/textures/gui/sprites/pets/squid.png              | Bin 0 -> 1684 bytes
 src/main/resources/assets/blade-addons/textures/gui/sprites/pets/subzero-wisp.png       | Bin 0 -> 2503 bytes
 src/main/resources/assets/blade-addons/textures/gui/sprites/pets/t-rex.png              | Bin 0 -> 2024 bytes
 src/main/resources/assets/blade-addons/textures/gui/sprites/pets/tarantula.png          | Bin 0 -> 2406 bytes
 src/main/resources/assets/blade-addons/textures/gui/sprites/pets/tiger.png              | Bin 0 -> 2505 bytes
 src/main/resources/assets/blade-addons/textures/gui/sprites/pets/turtle.png             | Bin 0 -> 3282 bytes
 src/main/resources/assets/blade-addons/textures/gui/sprites/pets/wither-skeleton.png    | Bin 0 -> 1811 bytes
 src/main/resources/assets/blade-addons/textures/gui/sprites/pets/wolf.png               | Bin 0 -> 2590 bytes
 src/main/resources/assets/blade-addons/textures/gui/sprites/pets/zombie.png             | Bin 0 -> 1931 bytes
 src/main/resources/blade-addons.mixins.json                                             |   5 +-
 126 files changed, 960 insertions(+), 201 deletions(-)
diff --git a/gradle.properties b/gradle.properties
index af9a288..407d8e9 100644
--- a/gradle.properties
+++ b/gradle.properties
@@ -13,12 +13,12 @@ loader_version=0.17.3
 loom_version=1.13-SNAPSHOT
 
 # Mod Properties
-mod_version=2.2.3
+mod_version=2.2.4
 maven_group=blade.addon
 archives_base_name=blade-addons
 
 # Dependencies
 fabric_version=0.136.0+1.21.8
-practical_config=1.3.3
+practical_config=963fde881f048a8ffdcb1468ba076a7985d135c1
 modmenu_version = 15.0.0
 hypixel_api_version = 1.0.1
\ No newline at end of file
diff --git a/src/main/java/blade/addon/Bladeaddons.java b/src/main/java/blade/addon/Bladeaddons.java
index 90e88ea..03ce519 100644
--- a/src/main/java/blade/addon/Bladeaddons.java
+++ b/src/main/java/blade/addon/Bladeaddons.java
@@ -1,6 +1,6 @@
 package blade.addon;
 
-import blade.addon.features.DianaNotifier;
+import blade.addon.features.other.DianaNotifier;
 import blade.addon.features.dungeon.AutoRequeue;
 import blade.addon.features.dungeon.ChestCounter;
 import blade.addon.features.dungeon.DeathTickTimer;
@@ -16,12 +16,14 @@ import blade.addon.features.dungeon.WarpCooldown;
 import blade.addon.features.dungeon.f7.BossWaypoints;
 import blade.addon.features.dungeon.f7.CrystalSpawn;
 import blade.addon.features.dungeon.f7.DistanceToLedge;
+import blade.addon.features.dungeon.f7.DragSpawnTimer;
 import blade.addon.features.dungeon.f7.GoldorTickTimer;
 import blade.addon.features.dungeon.f7.InvincibilityTimer;
 import blade.addon.features.dungeon.f7.PositionMessages;
 import blade.addon.features.dungeon.f7.RelicTimer;
 import blade.addon.features.dungeon.f7.StormTickTimer;
 import blade.addon.features.dungeon.f7.TermStartTimer;
+import blade.addon.features.other.SelectedPet;
 import blade.addon.utils.Commands;
 import blade.addon.utils.Debug;
 import blade.addon.utils.Keybinds;
@@ -69,5 +71,7 @@ public class Bladeaddons implements ModInitializer {
         ItemHighlight.init();
         BossWaypoints.init();
         DianaNotifier.init();
+        DragSpawnTimer.init();
+        SelectedPet.init();
     }
 }
\ No newline at end of file
diff --git a/src/main/java/blade/addon/features/dungeon/HidePlayers.java b/src/main/java/blade/addon/features/dungeon/HidePlayers.java
index 425bfd9..8c1cc45 100644
--- a/src/main/java/blade/addon/features/dungeon/HidePlayers.java
+++ b/src/main/java/blade/addon/features/dungeon/HidePlayers.java
@@ -47,6 +47,9 @@ public class HidePlayers {
     public static boolean shouldHidePlayers(PlayerEntity player) {
         if (!Location.inDungeon()) return false;
 
+        //in case it's a mob instead of a player
+        if (player.getName().getString().contains(" ")) return false;
+
         if (testHideAtLeap()) {
             return true;
         }
diff --git a/src/main/java/blade/addon/features/dungeon/f7/BossWaypoints.java b/src/main/java/blade/addon/features/dungeon/f7/BossWaypoints.java
index 2dd8eeb..889fd3f 100644
--- a/src/main/java/blade/addon/features/dungeon/f7/BossWaypoints.java
+++ b/src/main/java/blade/addon/features/dungeon/f7/BossWaypoints.java
@@ -22,7 +22,6 @@ import net.minecraft.client.util.math.MatrixStack;
 import net.minecraft.entity.player.PlayerEntity;
 import net.minecraft.text.Text;
 import net.minecraft.util.ActionResult;
-import net.minecraft.util.Formatting;
 import net.minecraft.util.Hand;
 import net.minecraft.util.hit.BlockHitResult;
 import net.minecraft.util.math.BlockPos;
@@ -37,9 +36,6 @@ import java.util.concurrent.CopyOnWriteArrayList;
 
 public class BossWaypoints {
 
-    private static final Text ON = Text.literal("ON").formatted(Formatting.GREEN);
-    private static final Text OFF = Text.literal("OFF").formatted(Formatting.RED);
-
     private static final String filePath = "./config/" + Constants.NAMESPACE + "-waypoints.json";
     private static final String WAYPOINTS_NAME = "waypoints";
 
@@ -69,12 +65,12 @@ public class BossWaypoints {
 
     public static void setPlace(boolean shouldPlace) {
         place = shouldPlace;
-        Misc.addChatMessage(Text.literal("Edit mode: ").append(place ? ON : OFF));
+        Misc.addChatMessage(Text.literal("Edit mode: ").append(Misc.getStatusText(place)));
     }
 
     public static void setIgnoreBoss(boolean shouldIgnoreBoss) {
         ignoreBoss = shouldIgnoreBoss;
-        Misc.addChatMessage(Text.literal("Ignore boss: ").append(ignoreBoss ? ON : OFF));
+        Misc.addChatMessage(Text.literal("Ignore boss: ").append(Misc.getStatusText(ignoreBoss)));
     }
 
     public static void togglePlace() {
diff --git a/src/main/java/blade/addon/features/dungeon/f7/CrystalSpawn.java b/src/main/java/blade/addon/features/dungeon/f7/CrystalSpawn.java
index 4451eae..895723a 100644
--- a/src/main/java/blade/addon/features/dungeon/f7/CrystalSpawn.java
+++ b/src/main/java/blade/addon/features/dungeon/f7/CrystalSpawn.java
@@ -7,7 +7,6 @@ import blade.addon.utils.dungeon.Phase;
 import blade.addon.utils.events.Events;
 import blade.addon.utils.rendering.RenderUtils;
 import config.practical.hud.HUDComponent;
-import net.fabricmc.fabric.api.client.message.v1.ClientReceiveMessageEvents;
 import net.minecraft.client.MinecraftClient;
 import net.minecraft.client.gui.DrawContext;
 import net.minecraft.text.Text;
@@ -26,12 +25,12 @@ public class CrystalSpawn {
 
     public static void init() {
 
-        ClientReceiveMessageEvents.GAME.register((message, overlay) -> {
+        Events.ON_GAME_MESSAGE.register(text -> {
             if (!Location.inDungeon() || !Phase.inP1() || !Floor7.enableCrystalSpawnTime) return;
 
-            Matcher matcher = PATTERN_1.matcher(message.getString());
+            Matcher matcher = PATTERN_1.matcher(text.getString());
             if (!matcher.find()) {
-                matcher = PATTERN_2.matcher(message.getString());
+                matcher = PATTERN_2.matcher(text.getString());
 
                 if (!matcher.find()) return;
             }
diff --git a/src/main/java/blade/addon/features/dungeon/f7/DragSpawnTimer.java b/src/main/java/blade/addon/features/dungeon/f7/DragSpawnTimer.java
new file mode 100644
index 0000000..7d1b784
--- /dev/null
+++ b/src/main/java/blade/addon/features/dungeon/f7/DragSpawnTimer.java
@@ -0,0 +1,167 @@
+package blade.addon.features.dungeon.f7;
+
+import blade.addon.utils.Constants;
+import blade.addon.utils.Scheduler;
+import blade.addon.utils.config.values.Floor7;
+import blade.addon.utils.dungeon.DungeonClass;
+import blade.addon.utils.events.Events;
+import blade.addon.utils.rendering.RenderUtils;
+import config.practical.hud.HUDComponent;
+import net.minecraft.client.MinecraftClient;
+import net.minecraft.client.gui.DrawContext;
+import net.minecraft.particle.ParticleTypes;
+import net.minecraft.sound.SoundEvents;
+
+public class DragSpawnTimer {
+
+    enum Dragon {
+        PURPLE(0xffff55ff, 0, 4),
+        BLUE(0xff55ffff, 1, 3),
+        RED(0xffff5555, 2, 2),
+        GREEN(0xff55ff55, 3, 1),
+        ORANGE(0xffffaa00, 4, 0),
+        NONE(0xffffffff, 99, 99);
+
+        final int color;
+        final int archPrio;
+        final int bersPrio;
+
+        Dragon(int color, int archPrio, int bersPrio) {
+            this.color = color;
+            this.archPrio = archPrio;
+            this.bersPrio = bersPrio;
+        }
+    }
+
+    public enum Team {
+        ARCHER_TEAM("Archer team"), BERS_TEAM("Bers team");
+
+        private final String label;
+
+        Team(String label) {
+            this.label = label;
+        }
+
+        @Override
+        public String toString() {
+            return label;
+        }
+    }
+
+    private static final int SPAWN_DURATION = 100;
+    private static final int DUPE_DELAY = 130;
+
+    private static Dragon currentDragon = Dragon.NONE;
+    private static Dragon prevDragon = Dragon.NONE;
+    private static boolean hasDoneSplit = false;
+    private static int tick = 0;
+    private static int dupeTick = 0;
+
+    public static void init() {
+        Events.ON_PARTICLE.register((x, y, z, effect) -> {
+            if (effect.getType() != ParticleTypes.FLAME) return;
+            Dragon detectedDragon = getDragon(x, y, z);
+
+            if (detectedDragon == Dragon.NONE) return;
+
+            //the dupe check is to stop purple from just breaking it when it spawns
+            if (currentDragon == Dragon.NONE) {
+                if ((prevDragon == detectedDragon && dupeTick > 0)) return;
+                currentDragon = detectedDragon;
+                prevDragon = currentDragon;
+                dupeTick = DUPE_DELAY;
+                tick = SPAWN_DURATION;
+                if (Floor7.sendSoundOnDragSpawn) {
+                    Scheduler.scheduleSound(SoundEvents.BLOCK_NOTE_BLOCK_PLING.value(), 0.75f, 1);
+                }
+            } else if (detectedDragon != currentDragon && !hasDoneSplit) {
+                currentDragon = getPrio(detectedDragon, currentDragon);
+                prevDragon = currentDragon;
+                dupeTick = DUPE_DELAY + 50;
+                hasDoneSplit = true;
+            }
+        });
+
+        Events.ON_SERVER_TICK.register(() -> {
+            tick = Math.max(tick - 1, 0);
+            dupeTick = Math.max(dupeTick - 1, 0);
+            if (dupeTick == 0 && prevDragon != Dragon.NONE) {
+                prevDragon = Dragon.NONE;
+            }
+
+            if (tick == 0 && currentDragon != Dragon.NONE) {
+                currentDragon = Dragon.NONE;
+            }
+        });
+
+        Events.ON_LOCATION_CHANGE.register(newLocation -> reset());
+    }
+
+    private static void reset() {
+        hasDoneSplit = false;
+        currentDragon = Dragon.NONE;
+        prevDragon = Dragon.NONE;
+        tick = 0;
+    }
+
+    //checks are from valley addons
+    private static Dragon getDragon(double x, double y, double z) {
+        // check if correct height
+        if (y >= 14 && y <= 19) {
+            // check if red/green
+            if (x >= 27 && x <= 32) {
+                // check if red
+                if (z == 59) {
+                    return Dragon.RED;
+                    // check if green
+                } else if (z == 94) {
+                    return Dragon.GREEN;
+                }
+                // check if blue/orange
+            } else if (x >= 79 && x <= 85) {
+                // check if blue
+                if (z == 94) {
+                    return Dragon.BLUE;
+                    // check if orange
+                } else if (z == 56) {
+                    return Dragon.ORANGE;
+                }
+                // check if purple
+            } else if (x == 56) {
+                return Dragon.PURPLE;
+            }
+        }
+        return Dragon.NONE;
+    }
+
+    private static Dragon getPrio(Dragon dragon1, Dragon dragon2) {
+        if (DungeonClass.isArchTeam()) {
+            if (dragon1.archPrio < dragon2.archPrio) {
+                return dragon1;
+            } else {
+                return dragon2;
+            }
+        } else if (DungeonClass.isBersTeam()) {
+            if (dragon1.bersPrio < dragon2.bersPrio) {
+                return dragon1;
+            } else {
+                return dragon2;
+            }
+        }
+        return Dragon.NONE;
+    }
+
+    public static boolean display() {
+        return Floor7.dragSpawnTimers && currentDragon != Dragon.NONE;
+    }
+
+    public static void render(HUDComponent component, DrawContext context) {
+        int x = component.getScaledX();
+        int y = component.getScaledY();
+
+        double num = tick * Constants.TICK_DURATION;
+
+        RenderUtils.drawCenteredText(context, MinecraftClient.getInstance().textRenderer, Constants.DECIMAL_FORMAT.format(num), x, y, component.getWidth(), currentDragon.color);
+
+    }
+}
diff --git a/src/main/java/blade/addon/features/dungeon/f7/InvincibilityTimer.java b/src/main/java/blade/addon/features/dungeon/f7/InvincibilityTimer.java
index bf24432..ea67937 100644
--- a/src/main/java/blade/addon/features/dungeon/f7/InvincibilityTimer.java
+++ b/src/main/java/blade/addon/features/dungeon/f7/InvincibilityTimer.java
@@ -39,9 +39,6 @@ public class InvincibilityTimer {
     private static final Pattern BONZO_ON_HEAD_PATTERN = Pattern.compile("Bonzo's Mask");
     private static final Pattern SPIRIT_ON_HEAD_PATTERN = Pattern.compile("Spirit Mask");
 
-    private static final Pattern MANUAL_EQUIP_PET_PATTERN = Pattern.compile("^You summoned your (\\D+)( ✦)?!$");
-    private static final Pattern RULE_EQUIP_PET_PATTERN = Pattern.compile("^Autopet equipped your \\[Lvl [0-9]+] (\\D+)( ✦)?! VIEW RULE$");
-
     //in ticks
     private static final int BONZO_MASK_COOLDOWN = 180 * 20;
     private static final int SPIRIT_MASK_COOLDOWN = 30 * 20;
@@ -72,7 +69,6 @@ public class InvincibilityTimer {
 
     public static void init() {
         Events.ON_SERVER_TICK.register(InvincibilityTimer::tick);
-        ClientReceiveMessageEvents.GAME.register(InvincibilityTimer::detectPet);
         Events.ON_SLOT_CHANGE.register(InvincibilityTimer::detectHelmet);
 
         Events.ON_LOCATION_CHANGE.register(newLocation -> {
@@ -83,6 +79,9 @@ public class InvincibilityTimer {
             }
         });
 
+        Events.ON_PET.register(name -> phoenixOn = name.equals("Phoenix"));
+
+
         ClientReceiveMessageEvents.GAME.register((message, overlay) -> {
             if (Location.inDungeon()) {
                 parseMessage(message);
@@ -96,22 +95,6 @@ public class InvincibilityTimer {
         phoenixTicks = Math.max(0, phoenixTicks - 1);
     }
 
-    private static void detectPet(Text message, boolean overlay) {
-        if (!Location.inDungeon()) return;
-        String string = message.getString().replaceAll("§.", "");
-        Matcher matcher = MANUAL_EQUIP_PET_PATTERN.matcher(string);
-        if (matcher.find()) {
-            String petName = matcher.group(1);
-            phoenixOn = petName.contains("Phoenix");
-        }
-
-        matcher = RULE_EQUIP_PET_PATTERN.matcher(string);
-        if (matcher.find()) {
-            String petName = matcher.group(1);
-            phoenixOn = petName.contains("Phoenix");
-        }
-    }
-
     private static void detectHelmet(int slot, ItemStack item) {
         if (slot != HEAD_SLOT || !Location.inDungeon()) return;
         Text text = item.getCustomName();
diff --git a/src/main/java/blade/addon/features/dungeon/f7/RelicTimer.java b/src/main/java/blade/addon/features/dungeon/f7/RelicTimer.java
index 228316c..d84b1b9 100644
--- a/src/main/java/blade/addon/features/dungeon/f7/RelicTimer.java
+++ b/src/main/java/blade/addon/features/dungeon/f7/RelicTimer.java
@@ -11,10 +11,6 @@ import blade.addon.utils.rendering.RenderUtils;
 import config.practical.hud.HUDComponent;
 import net.fabricmc.fabric.api.client.message.v1.ClientReceiveMessageEvents;
 import net.fabricmc.fabric.api.client.rendering.v1.WorldRenderEvents;
-import net.fabricmc.fabric.api.event.player.AttackBlockCallback;
-import net.fabricmc.fabric.api.event.player.UseBlockCallback;
-import net.minecraft.block.BlockState;
-import net.minecraft.block.Blocks;
 import net.minecraft.client.MinecraftClient;
 import net.minecraft.client.gui.DrawContext;
 import net.minecraft.client.network.ClientPlayerEntity;
@@ -24,7 +20,6 @@ import net.minecraft.client.render.VertexConsumerProvider;
 import net.minecraft.client.render.VertexRendering;
 import net.minecraft.client.util.math.MatrixStack;
 import net.minecraft.text.Text;
-import net.minecraft.util.ActionResult;
 import net.minecraft.util.Formatting;
 import net.minecraft.util.math.BlockPos;
 import net.minecraft.util.math.Box;
@@ -97,20 +92,30 @@ public class RelicTimer {
             }
         });
 
-        AttackBlockCallback.EVENT.register((playerEntity, world, hand, blockPos, direction) -> {
-            if (world == null) return ActionResult.PASS;
-            if (!Location.inDungeon() || !Phase.inP5() || pickedupRelic == null) return ActionResult.PASS;
-            BlockState block = world.getBlockState(blockPos);
-            onBlockClick(block, blockPos);
-            return ActionResult.PASS;
-        });
-
-        UseBlockCallback.EVENT.register((player, world, hand, hitResult) -> {
-            if (world == null) return ActionResult.PASS;
-            if (!Location.inDungeon() || !Phase.inP5() || pickedupRelic == null) return ActionResult.PASS;
-            BlockState block = world.getBlockState(hitResult.getBlockPos());
-            onBlockClick(block, hitResult.getBlockPos());
-            return ActionResult.PASS;
+        Events.ON_BLOCK_INTERACTION.register((result, itemStack) -> {
+            if (!Location.inDungeon() || !Phase.inP5() || pickedupRelic == null) return false;
+            BlockPos pos = result.getBlockPos();
+
+            Text name = itemStack.getCustomName();
+            if (name == null) return false;
+            if (!name.getString().contains("Relic")) return false;
+
+            if (pos.getX() == pickedupRelic.box.minX && (pos.getY() == pickedupRelic.box.minY || pos.getY() == pickedupRelic.box.minY - 1) && pos.getZ() == pickedupRelic.box.minZ) {
+                if (Floor7.enableRelicPlaceTime) {
+                    double diff = (System.currentTimeMillis() - pickupTime) / 1000.0;
+                    Misc.addChatMessage(Text.literal("The ").formatted(Formatting.GREEN)
+                            .append(Text.literal(pickedupRelic.name().toLowerCase()).withColor(pickedupRelic.color))
+                            .append(" relic was placed in ").formatted(Formatting.GREEN)
+                            .append(Text.literal(Constants.DECIMAL_FORMAT.format(diff) + "s.").formatted(Formatting.YELLOW)));
+                }
+                pickedupRelic = null;
+            } else {
+                if (Floor7.blockIncorrectRelicPlace) {
+                    Misc.addChatMessage(Text.literal("incorrect click!"));
+                    return true;
+                }
+            }
+            return false;
         });
 
         WorldRenderEvents.BEFORE_DEBUG_RENDER.register(worldRenderContext -> {
@@ -139,28 +144,11 @@ public class RelicTimer {
 
     }
 
-    private static void onBlockClick(BlockState block, BlockPos pos) {
-        if (block.getBlock() != Blocks.ANVIL && block.getBlock() != Blocks.CAULDRON) return;
-        double distance = pickedupRelic.box.getCenter().distanceTo(pos.toCenterPos());
-        if (distance <= 1) {
-            if (Floor7.enableRelicPlaceTime) {
-            double diff = (System.currentTimeMillis() - pickupTime) / 1000.0;
-                Misc.addChatMessage(Text.literal("The ").formatted(Formatting.GREEN)
-                        .append(Text.literal(pickedupRelic.name().toLowerCase()).withColor(pickedupRelic.color))
-                        .append(" relic was placed in ").formatted(Formatting.GREEN)
-                        .append(Text.literal(Constants.DECIMAL_FORMAT.format(diff) + "s.").formatted(Formatting.YELLOW)));
-            }
-            pickedupRelic = null;
-        }
-
-
-    }
-
     public static boolean display() {
-        return  Floor7.enableRelicStartTimer && Location.inDungeon() && Phase.inP5() && tick > 0;
+        return Floor7.enableRelicStartTimer && Location.inDungeon() && Phase.inP5() && tick > 0;
     }
 
-    public static void render(HUDComponent component, DrawContext context)  {
+    public static void render(HUDComponent component, DrawContext context) {
         int x = component.getScaledX();
         int y = component.getScaledY();
 
diff --git a/src/main/java/blade/addon/features/dungeon/f7/StormTickTimer.java b/src/main/java/blade/addon/features/dungeon/f7/StormTickTimer.java
index d89e095..c1f5911 100644
--- a/src/main/java/blade/addon/features/dungeon/f7/StormTickTimer.java
+++ b/src/main/java/blade/addon/features/dungeon/f7/StormTickTimer.java
@@ -3,13 +3,13 @@ package blade.addon.features.dungeon.f7;
 import blade.addon.utils.Constants;
 import blade.addon.utils.Location;
 import blade.addon.utils.Misc;
+import blade.addon.utils.Scheduler;
 import blade.addon.utils.config.values.Floor7;
 import blade.addon.utils.dungeon.DungeonClass;
 import blade.addon.utils.dungeon.Phase;
 import blade.addon.utils.events.Events;
 import blade.addon.utils.rendering.RenderUtils;
 import config.practical.hud.HUDComponent;
-import net.fabricmc.fabric.api.client.message.v1.ClientReceiveMessageEvents;
 import net.minecraft.client.MinecraftClient;
 import net.minecraft.client.gui.DrawContext;
 import net.minecraft.text.Text;
@@ -49,19 +49,22 @@ public class StormTickTimer {
             }
         });
 
-        ClientReceiveMessageEvents.GAME.register((message, overlay) -> {
+        Events.ON_GAME_MESSAGE.register(text -> {
             if (!Location.inDungeon() || !Phase.inP2() || !Floor7.enableStormDeathTime) return;
 
-            Matcher matcher = PATTERN.matcher(message.getString());
+            Matcher matcher = PATTERN.matcher(text.getString());
 
             if (matcher.find()) {
                 deathTime = (tick * Constants.TICK_DURATION);
                 deathStartDisplayTime = System.currentTimeMillis();
-                Misc.addChatMessage(
+
+                //prob dosent need to schedule it but threads are scary
+                Scheduler.scheduleTask(() -> Misc.addChatMessage(
                         Text.literal("Storm died at: ").formatted(Formatting.GREEN)
                                 .append(Text.literal(Constants.DECIMAL_FORMAT.format(deathTime) + "s").formatted(Formatting.YELLOW))
                                 .append(Text.literal(".").formatted(Formatting.GREEN))
-                );
+                ), 1);
+
             }
 
         });
@@ -87,7 +90,7 @@ public class StormTickTimer {
 
         }
 
-        RenderUtils.drawCenteredText(context, MinecraftClient.getInstance().textRenderer, Constants.DECIMAL_FORMAT.format(num), x, y, component.getWidth());
+        RenderUtils.drawCenteredText(context, MinecraftClient.getInstance().textRenderer, Constants.DECIMAL_FORMAT.format(num), x, y, component.getWidth(), Floor7.stormTickTimerColor);
 
     }
 
diff --git a/src/main/java/blade/addon/features/item/ItemRarityDrawer.java b/src/main/java/blade/addon/features/item/ItemRarityDrawer.java
deleted file mode 100644
index e0d2c1d..0000000
--- a/src/main/java/blade/addon/features/item/ItemRarityDrawer.java
+++ /dev/null
@@ -1,23 +0,0 @@
-package blade.addon.features.item;
-
-import blade.addon.utils.Misc;
-import net.minecraft.client.gui.DrawContext;
-import net.minecraft.item.ItemStack;
-
-public class ItemRarityDrawer {
-
-    public static void draw(DrawContext context, ItemStack stack, int x, int y) {
-        ItemRarityHolder holder = (ItemRarityHolder) (Object) stack;
-        assert holder != null;
-
-        if (!holder.blade_addons$hasScanned()) {
-            ItemRarity rarity = Misc.getRarity(stack);
-            holder.blade_addons$setItemRarity(rarity);
-        }
-
-        if (!holder.blade_addons$hasItemRarity()) return;
-
-        ItemRarity rarity = holder.blade_addons$getItemRarity();
-        context.fill(x, y, x + 16, y + 16, rarity.getColor());
-    }
-}
diff --git a/src/main/java/blade/addon/features/item/ItemRarityHighlight.java b/src/main/java/blade/addon/features/item/ItemRarityHighlight.java
new file mode 100644
index 0000000..ea21293
--- /dev/null
+++ b/src/main/java/blade/addon/features/item/ItemRarityHighlight.java
@@ -0,0 +1,50 @@
+package blade.addon.features.item;
+
+import net.minecraft.client.gui.DrawContext;
+import net.minecraft.component.DataComponentTypes;
+import net.minecraft.component.type.LoreComponent;
+import net.minecraft.item.ItemStack;
+import net.minecraft.text.Text;
+
+import java.util.List;
+
+public class ItemRarityHighlight {
+
+    public static void draw(DrawContext context, ItemStack stack, int x, int y) {
+        ItemRarityHolder holder = (ItemRarityHolder) (Object) stack;
+        assert holder != null;
+
+        if (!holder.blade_addons$hasScanned()) {
+            ItemRarity rarity = getRarity(stack);
+            holder.blade_addons$setItemRarity(rarity);
+        }
+
+        if (!holder.blade_addons$hasItemRarity()) return;
+
+        ItemRarity rarity = holder.blade_addons$getItemRarity();
+        context.fill(x, y, x + 16, y + 16, rarity.getColor());
+    }
+
+    public static ItemRarity getRarity(ItemStack item) {
+        LoreComponent lore = item.get(DataComponentTypes.LORE);
+        if (lore == null)  return ItemRarity.NONE;
+
+        List<Text> lines = lore.lines();
+        if (lines.isEmpty()) return ItemRarity.NONE;
+        Text line = lines.getLast();
+
+        if (line == null) return ItemRarity.NONE;
+        String string = line.getString();
+        String[] rarityStrings = string.split(" ");
+
+        for (String testString: rarityStrings) {
+            try {
+                ItemRarity rarity = ItemRarity.valueOf(testString);
+                return rarity;
+            } catch (IllegalArgumentException ignored) {
+
+            }
+        }
+        return ItemRarity.NONE;
+    }
+}
diff --git a/src/main/java/blade/addon/features/item/PetHolder.java b/src/main/java/blade/addon/features/item/PetHolder.java
new file mode 100644
index 0000000..ea67032
--- /dev/null
+++ b/src/main/java/blade/addon/features/item/PetHolder.java
@@ -0,0 +1,14 @@
+package blade.addon.features.item;
+
+public interface PetHolder {
+
+    boolean selectedPet = false;
+    boolean scannedPet = false;
+
+    boolean blade_addons$hasScannedPet();
+
+    void blade_addons$setSelected(boolean selected);
+
+    boolean blade_addons$isSelected();
+
+}
diff --git a/src/main/java/blade/addon/features/item/SelectedPetHighlight.java b/src/main/java/blade/addon/features/item/SelectedPetHighlight.java
new file mode 100644
index 0000000..c00db1b
--- /dev/null
+++ b/src/main/java/blade/addon/features/item/SelectedPetHighlight.java
@@ -0,0 +1,39 @@
+package blade.addon.features.item;
+
+import net.minecraft.client.gui.DrawContext;
+import net.minecraft.component.DataComponentTypes;
+import net.minecraft.component.type.LoreComponent;
+import net.minecraft.item.ItemStack;
+import net.minecraft.text.Text;
+
+import java.util.List;
+
+public class SelectedPetHighlight {
+
+    public static void draw(DrawContext context, ItemStack stack, int x, int y) {
+        PetHolder holder = (PetHolder) (Object) stack;
+        assert holder != null;
+
+        if (!holder.blade_addons$hasScannedPet()) {
+            holder.blade_addons$setSelected(isSelected(stack));
+        }
+
+        if (!holder.blade_addons$isSelected()) return;
+
+        context.fill(x, y, x + 16, y + 16, 0xffff0000);
+    }
+
+    public static boolean isSelected(ItemStack item) {
+        LoreComponent lore = item.get(DataComponentTypes.LORE);
+        if (lore == null)  return false;
+
+        List<Text> lines = lore.lines();
+        if (lines.isEmpty()) return false;
+
+        for(Text text: lines) {
+            if (text == null) continue;
+            if (text.getString().equals("Click to despawn!")) return true;
+        }
+        return false;
+    }
+}
diff --git a/src/main/java/blade/addon/features/DianaNotifier.java b/src/main/java/blade/addon/features/other/DianaNotifier.java
similarity index 88%
rename from src/main/java/blade/addon/features/DianaNotifier.java
rename to src/main/java/blade/addon/features/other/DianaNotifier.java
index 77f250d..a51109d 100644
--- a/src/main/java/blade/addon/features/DianaNotifier.java
+++ b/src/main/java/blade/addon/features/other/DianaNotifier.java
@@ -1,5 +1,7 @@
-package blade.addon.features;
+package blade.addon.features.other;
 
+import blade.addon.utils.Debug;
+import blade.addon.utils.Location;
 import blade.addon.utils.Misc;
 import blade.addon.utils.Scheduler;
 import blade.addon.utils.events.Events;
@@ -24,6 +26,7 @@ public class DianaNotifier {
 
     public static void init() {
         Events.ON_GAME_MESSAGE.register(message-> {
+            if (!Location.in(Location.HUB)) return;
             String string = message.getString();
             String lowerCase = string.toLowerCase();
             if (!lowerCase.contains("you dug out a")) return;
@@ -48,7 +51,7 @@ public class DianaNotifier {
             Misc.setTitle(Text.literal(name));
             ClientPlayerEntity player = client.player;
             if (player == null) {
-                Misc.addChatMessage(Text.literal("Player is somehow null"));
+                Debug.sendDebugMessage(Text.literal("Player is null when checking diana message"));
                 return;
             }
 
diff --git a/src/main/java/blade/addon/features/other/SelectedPet.java b/src/main/java/blade/addon/features/other/SelectedPet.java
new file mode 100644
index 0000000..40971e7
--- /dev/null
+++ b/src/main/java/blade/addon/features/other/SelectedPet.java
@@ -0,0 +1,159 @@
+package blade.addon.features.other;
+
+import blade.addon.utils.Constants;
+import blade.addon.utils.Debug;
+import blade.addon.utils.config.values.ExtraOptions;
+import blade.addon.utils.events.Events;
+import config.practical.hud.HUDComponent;
+import net.fabricmc.fabric.api.client.message.v1.ClientReceiveMessageEvents;
+import net.minecraft.client.MinecraftClient;
+import net.minecraft.client.font.TextRenderer;
+import net.minecraft.client.gl.RenderPipelines;
+import net.minecraft.client.gui.DrawContext;
+import net.minecraft.text.Style;
+import net.minecraft.text.Text;
+import net.minecraft.util.Formatting;
+import net.minecraft.util.Identifier;
+
+import java.util.List;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+public class SelectedPet {
+
+    private static final int COLOR_OFFSET = 2;
+
+    private static final Pattern TAB_PET_PATTERN = Pattern.compile(" \\[Lvl \\d+] (\\D+)");
+    private static final Pattern MANUAL_EQUIP_PET_PATTERN = Pattern.compile("^You (summoned|despawned) your (\\D+)!$");
+    private static final Pattern RULE_EQUIP_PET_PATTERN = Pattern.compile("^Autopet equipped your \\[Lvl [0-9]+] (\\D+)! VIEW RULE$");
+
+    private static final Text NO_PET = Text.literal("No pet").formatted(Formatting.RED);
+    private static final String identifierPrefix = "pets/";
+
+    private static Text currentPetText = NO_PET;
+    private static String currentPetString = "";
+    private static Identifier spriteId = null;
+
+    public static void init() {
+        ClientReceiveMessageEvents.GAME.register((message, overlay) -> {
+            String string = message.getString();
+            Matcher matcher = MANUAL_EQUIP_PET_PATTERN.matcher(string);
+            if (matcher.find()) {
+
+                if (matcher.group(1).equals("despawned")) {
+                    despawnPet();
+                    return;
+                }
+
+                String name = matcher.group(2).replace(" ✦", "");
+                Style style = getStyle(message, name);
+                summonPet(name, Text.literal(name).setStyle(style));
+                return;
+            }
+
+            matcher = RULE_EQUIP_PET_PATTERN.matcher(string.replaceAll("§.", ""));
+            if (matcher.find()) {
+                String name = matcher.group(1).replace(" ✦", "");
+
+                String textName = name;
+                int index = string.indexOf(name);
+                if (index > COLOR_OFFSET) {
+                    textName = string.substring(index - COLOR_OFFSET, index) + textName;
+                }
+
+                summonPet(name, Text.literal(textName));
+            }
+        });
+
+        Events.ON_PLAYER_ENTRY.register(receivedEntry -> {
+            if (receivedEntry == null) return;
+            Text text = receivedEntry.displayName();
+            if (text == null) return;
+
+            String string = text.getString();
+            if (string.equals(" No pet selected")) {
+                despawnPet();
+                return;
+            }
+
+            Matcher matcher = TAB_PET_PATTERN.matcher(string);
+            if (matcher.find()) {
+                String petName = matcher.group(1).replace(" ✦", "");
+                Style style = getStyle(text, petName);
+                summonPet(petName, Text.literal(petName).setStyle(style));
+            }
+        });
+    }
+
+    private static void despawnPet() {
+
+        if (!currentPetString.isEmpty() && Events.ON_PET.hasListeners()) {
+            Events.ON_PET.invoke(petEvent -> petEvent.onPet(""));
+        }
+
+        currentPetText = NO_PET;
+        currentPetString = "";
+        spriteId = null;
+
+    }
+
+    private static void summonPet(String stringName, Text textName) {
+        if (stringName.equals(currentPetString)) {
+            return;
+        }
+
+        currentPetText = textName;
+        currentPetString = stringName;
+
+        if (Events.ON_PET.hasListeners()) {
+            Events.ON_PET.invoke(petEvent -> petEvent.onPet(currentPetString));
+        }
+
+        updateSprite(stringName);
+    }
+
+    private static Style getStyle(Text text, String name) {
+        Style style = Style.EMPTY;
+        List<Text> lines = text.getSiblings();
+
+        for (Text line : lines) {
+            if (line.getString().equals(name)) {
+                style = line.getStyle();
+            }
+        }
+
+        return style;
+    }
+
+    private static void updateSprite(String petName) {
+        if (petName == null) {
+            spriteId = null;
+            return;
+        }
+        String formattedName = petName.toLowerCase().replace(" ", "-");
+        if (Identifier.isNamespaceValid(formattedName)) {
+            spriteId = Identifier.of(Constants.NAMESPACE, identifierPrefix + formattedName);
+        } else {
+            Debug.LOGGER.warn("{} includes invalid chars", formattedName);
+        }
+    }
+
+    public static boolean display() {
+        return ExtraOptions.drawPetHUD;
+    }
+
+    public static void render(HUDComponent component, DrawContext context) {
+        int x = component.getScaledX();
+        int y = component.getScaledY();
+
+        if (spriteId != null && ExtraOptions.includePetSprite) {
+            context.drawGuiTexture(RenderPipelines.GUI_TEXTURED, spriteId, x, y, 16, 16, 0xffffffff);
+        }
+
+        TextRenderer textRenderer = MinecraftClient.getInstance().textRenderer;
+
+        context.drawText(textRenderer, currentPetText, x + (ExtraOptions.includePetSprite? 18: 0), y + component.getHeight() - textRenderer.fontHeight, 0xffffffff, true);
+
+    }
+
+}
diff --git a/src/main/java/blade/addon/mixin/EntityMixin.java b/src/main/java/blade/addon/mixin/EntityMixin.java
new file mode 100644
index 0000000..0369cde
--- /dev/null
+++ b/src/main/java/blade/addon/mixin/EntityMixin.java
@@ -0,0 +1,19 @@
+package blade.addon.mixin;
+
+import blade.addon.utils.config.values.ExtraOptions;
+import net.minecraft.entity.Entity;
+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.CallbackInfoReturnable;
+
+@Mixin(Entity.class)
+public class EntityMixin {
+
+    @Inject(method = "isGlowing", at=@At("HEAD"), cancellable = true)
+    public void isGlowing(CallbackInfoReturnable<Boolean> cir) {
+        if (ExtraOptions.disableGlowing) {
+                cir.setReturnValue(false);
+        }
+    }
+}
diff --git a/src/main/java/blade/addon/mixin/HandledScreenMixin.java b/src/main/java/blade/addon/mixin/HandledScreenMixin.java
index 740c93f..296f1eb 100644
--- a/src/main/java/blade/addon/mixin/HandledScreenMixin.java
+++ b/src/main/java/blade/addon/mixin/HandledScreenMixin.java
@@ -1,11 +1,14 @@
 package blade.addon.mixin;
 
-import blade.addon.features.item.ItemRarityDrawer;
+import blade.addon.features.item.ItemRarityHighlight;
+import blade.addon.features.item.SelectedPetHighlight;
 import blade.addon.utils.config.values.ExtraOptions;
 import net.minecraft.client.gui.DrawContext;
+import net.minecraft.client.gui.screen.ingame.GenericContainerScreen;
 import net.minecraft.client.gui.screen.ingame.HandledScreen;
 import net.minecraft.item.ItemStack;
 import net.minecraft.screen.slot.Slot;
+import net.minecraft.text.Text;
 import org.spongepowered.asm.mixin.Mixin;
 import org.spongepowered.asm.mixin.injection.At;
 import org.spongepowered.asm.mixin.injection.Inject;
@@ -13,13 +16,27 @@ import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
 
 @Mixin(HandledScreen.class)
 public class HandledScreenMixin {
-
     @Inject(method = "drawSlot", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/gui/DrawContext;drawItem(Lnet/minecraft/item/ItemStack;III)V"))
     public void drawBackground(DrawContext context, Slot slot, CallbackInfo ci) {
-        if (!ExtraOptions.itemRarityBackground) return;
+
         int x = slot.x;
         int y = slot.y;
         ItemStack stack = slot.getStack();
-        ItemRarityDrawer.draw(context, stack, x, y);
+
+        if (ExtraOptions.highlightSelectedPet) {
+            HandledScreen screen = (HandledScreen) (Object) this;
+            if (screen instanceof GenericContainerScreen containerScreen) {
+                Text title = containerScreen.getTitle();
+                if (title == null) return;
+                if (title.getString().equals("Pets")) {
+                    SelectedPetHighlight.draw(context, stack, x, y);
+                }
+
+            }
+        }
+
+        if (ExtraOptions.itemRarityBackground) {
+            ItemRarityHighlight.draw(context, stack, x, y);
+        }
     }
 }
diff --git a/src/main/java/blade/addon/mixin/InGameHudMixin.java b/src/main/java/blade/addon/mixin/InGameHudMixin.java
index bc0c073..0a666dd 100644
--- a/src/main/java/blade/addon/mixin/InGameHudMixin.java
+++ b/src/main/java/blade/addon/mixin/InGameHudMixin.java
@@ -1,6 +1,6 @@
 package blade.addon.mixin;
 
-import blade.addon.features.item.ItemRarityDrawer;
+import blade.addon.features.item.ItemRarityHighlight;
 import blade.addon.utils.config.values.ExtraOptions;
 import net.minecraft.client.gui.DrawContext;
 import net.minecraft.client.gui.hud.InGameHud;
@@ -18,6 +18,13 @@ public class InGameHudMixin {
     @Inject(method = "renderHotbarItem", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/gui/DrawContext;drawItem(Lnet/minecraft/entity/LivingEntity;Lnet/minecraft/item/ItemStack;III)V"))
     public void drawItem(DrawContext context, int x, int y, RenderTickCounter tickCounter, PlayerEntity player, ItemStack stack, int seed, CallbackInfo ci) {
         if (!ExtraOptions.itemRarityBackground) return;
-        ItemRarityDrawer.draw(context, stack, x, y);
+        ItemRarityHighlight.draw(context, stack, x, y);
     }
+
+     @Inject(method = "renderStatusEffectOverlay", at=@At("HEAD"), cancellable = true)
+    public void renderStatusEffectsOverLay(DrawContext context, RenderTickCounter tickCounter, CallbackInfo ci) {
+        if (ExtraOptions.hideStatusOverLay) {
+            ci.cancel();
+        }
+     }
 }
diff --git a/src/main/java/blade/addon/mixin/ItemStackMixin.java b/src/main/java/blade/addon/mixin/ItemStackMixin.java
index 0e5c0c1..0256f05 100644
--- a/src/main/java/blade/addon/mixin/ItemStackMixin.java
+++ b/src/main/java/blade/addon/mixin/ItemStackMixin.java
@@ -2,16 +2,23 @@ package blade.addon.mixin;
 
 import blade.addon.features.item.ItemRarityHolder;
 import blade.addon.features.item.ItemRarity;
+import blade.addon.features.item.PetHolder;
 import net.minecraft.item.ItemStack;
 import org.spongepowered.asm.mixin.Mixin;
 import org.spongepowered.asm.mixin.Unique;
 
 @Mixin(ItemStack.class)
-public class ItemStackMixin implements ItemRarityHolder {
+public class ItemStackMixin implements ItemRarityHolder, PetHolder {
 
     @Unique
     private ItemRarity itemRarity = null;
 
+    @Unique
+    boolean selectedPet = false;
+
+    @Unique
+    boolean scannedPet = false;
+
 
     @Override
     public ItemRarity blade_addons$getItemRarity() {
@@ -32,4 +39,20 @@ public class ItemStackMixin implements ItemRarityHolder {
     public boolean blade_addons$hasScanned() {
         return itemRarity != null;
     }
+
+    @Override
+    public boolean blade_addons$hasScannedPet() {
+        return scannedPet;
+    }
+
+    @Override
+    public void blade_addons$setSelected(boolean selected) {
+        selectedPet = selected;
+        scannedPet = true;
+    }
+
+    @Override
+    public boolean blade_addons$isSelected() {
+        return selectedPet;
+    }
 }
diff --git a/src/main/java/blade/addon/mixin/MinecraftClientMixin.java b/src/main/java/blade/addon/mixin/MinecraftClientMixin.java
new file mode 100644
index 0000000..644b89e
--- /dev/null
+++ b/src/main/java/blade/addon/mixin/MinecraftClientMixin.java
@@ -0,0 +1,34 @@
+package blade.addon.mixin;
+
+import blade.addon.utils.events.Events;
+import com.llamalad7.mixinextras.sugar.Local;
+import net.minecraft.client.MinecraftClient;
+import net.minecraft.item.ItemStack;
+import net.minecraft.util.hit.BlockHitResult;
+import net.minecraft.util.hit.HitResult;
+import org.jetbrains.annotations.Nullable;
+import org.spongepowered.asm.mixin.Mixin;
+import org.spongepowered.asm.mixin.Shadow;
+import org.spongepowered.asm.mixin.injection.At;
+import org.spongepowered.asm.mixin.injection.Inject;
+import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
+
+@Mixin(MinecraftClient.class)
+public class MinecraftClientMixin {
+
+    @Shadow
+    @Nullable
+    public HitResult crosshairTarget;
+
+    @Inject(method = "doItemUse", at = @At(value = "INVOKE_ASSIGN", target = "Lnet/minecraft/item/ItemStack;getCount()I"), cancellable = true)
+    private void testInteraction(CallbackInfo ci, @Local ItemStack itemStack) {
+        BlockHitResult blockHitResult = (BlockHitResult) this.crosshairTarget;
+
+        if (blockHitResult == null) return;
+        if (Events.ON_BLOCK_INTERACTION.test(blockInteractionEvent -> blockInteractionEvent.iteract(blockHitResult, itemStack))) {
+            ci.cancel();
+        }
+
+
+    }
+}
diff --git a/src/main/java/blade/addon/mixin/NetworkMixin.java b/src/main/java/blade/addon/mixin/NetworkMixin.java
index 6284c5d..22f17da 100644
--- a/src/main/java/blade/addon/mixin/NetworkMixin.java
+++ b/src/main/java/blade/addon/mixin/NetworkMixin.java
@@ -1,5 +1,8 @@
 package blade.addon.mixin;
 
+import blade.addon.utils.Debug;
+import blade.addon.utils.Misc;
+import blade.addon.utils.Scheduler;
 import blade.addon.utils.config.values.ExtraOptions;
 import blade.addon.utils.events.Events;
 import com.llamalad7.mixinextras.injector.ModifyExpressionValue;
@@ -15,6 +18,7 @@ import net.minecraft.network.packet.s2c.play.TeamS2CPacket;
 import net.minecraft.scoreboard.Team;
 import net.minecraft.sound.SoundEvent;
 import net.minecraft.sound.SoundEvents;
+import net.minecraft.text.Text;
 import org.spongepowered.asm.mixin.Mixin;
 import org.spongepowered.asm.mixin.Shadow;
 import org.spongepowered.asm.mixin.injection.At;
@@ -69,6 +73,10 @@ public class NetworkMixin {
         if (pitch == 0.0 && volume == 8.0 && event == SoundEvents.ENTITY_ENDERMAN_TELEPORT) {
             ci.cancel();
         }
+
+        if (Debug.sendSound) {
+            Scheduler.scheduleTask(() -> Misc.addChatMessage(Text.literal("Sound: " + event.id())), 1);
+        }
     }
 
 }
\ No newline at end of file
diff --git a/src/main/java/blade/addon/mixin/ServerTickMixin.java b/src/main/java/blade/addon/mixin/ServerTickMixin.java
index 33f0410..ad0a1d6 100644
--- a/src/main/java/blade/addon/mixin/ServerTickMixin.java
+++ b/src/main/java/blade/addon/mixin/ServerTickMixin.java
@@ -8,6 +8,8 @@ import net.minecraft.network.ClientConnection;
 import net.minecraft.network.packet.Packet;
 import net.minecraft.network.packet.s2c.common.CommonPingS2CPacket;
 import net.minecraft.network.packet.s2c.play.GameMessageS2CPacket;
+import net.minecraft.network.packet.s2c.play.ParticleS2CPacket;
+import net.minecraft.particle.ParticleEffect;
 import org.spongepowered.asm.mixin.Mixin;
 import org.spongepowered.asm.mixin.injection.At;
 import org.spongepowered.asm.mixin.injection.Inject;
@@ -31,6 +33,16 @@ public class ServerTickMixin {
             }
         }
 
+        if (packet instanceof ParticleS2CPacket particle) {
+            double x = particle.getX();
+            double y = particle.getY();
+            double z = particle.getZ();
+            ParticleEffect effect = particle.getParameters();
+            if (Events.ON_PARTICLE.hasListeners()) {
+                Events.ON_PARTICLE.invoke(particleEvent -> particleEvent.onParticle(x, y, z, effect));
+            }
+        }
+
     }
 
     @Inject(method = "sendImmediately", at = @At("HEAD"))
diff --git a/src/main/java/blade/addon/mixin/StatusEffectsDisplayMixin.java b/src/main/java/blade/addon/mixin/StatusEffectsDisplayMixin.java
new file mode 100644
index 0000000..dd6cb5f
--- /dev/null
+++ b/src/main/java/blade/addon/mixin/StatusEffectsDisplayMixin.java
@@ -0,0 +1,27 @@
+package blade.addon.mixin;
+
+import blade.addon.utils.config.values.ExtraOptions;
+import net.minecraft.client.gui.DrawContext;
+import net.minecraft.client.gui.screen.ingame.StatusEffectsDisplay;
+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;
+
+@Mixin(StatusEffectsDisplay.class)
+public class StatusEffectsDisplayMixin {
+
+    @Inject(method = "drawStatusEffects", at=@At("HEAD"), cancellable = true)
+    public void drawEffects(DrawContext context, int mouseX, int mouseY, CallbackInfo ci) {
+        if (ExtraOptions.hideStatusOverLay) {
+            ci.cancel();
+        }
+    }
+
+    @Inject(method = "drawStatusEffectTooltip", at=@At("HEAD"), cancellable = true)
+    public void drawToolTip(DrawContext context, int mouseX, int mouseY, CallbackInfo ci) {
+        if (ExtraOptions.hideStatusOverLay) {
+            ci.cancel();
+        }
+    }
+}
diff --git a/src/main/java/blade/addon/utils/Debug.java b/src/main/java/blade/addon/utils/Debug.java
index 7d348cd..c1eeaf1 100644
--- a/src/main/java/blade/addon/utils/Debug.java
+++ b/src/main/java/blade/addon/utils/Debug.java
@@ -4,10 +4,14 @@ import blade.addon.utils.dungeon.Phase;
 import blade.addon.utils.dungeon.Section;
 import net.fabricmc.fabric.api.client.command.v2.ClientCommandManager;
 import net.minecraft.text.Text;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
 
 public class Debug {
 
+    public static final Logger LOGGER = LoggerFactory.getLogger(Constants.NAMESPACE);
     private static boolean sendDebug = false;
+    public static boolean sendSound = false;
 
     public static void init() {
        registerCommands();
@@ -22,16 +26,19 @@ public class Debug {
 
         Commands.registerDevCommand(ClientCommandManager.literal("toggleSendDebug").executes(context -> {
             sendDebug = !sendDebug;
-            Misc.addChatMessage(Text.literal("Send debug: " + sendDebug));
+            Misc.addChatMessage(Text.literal("Send debug: ").append(Misc.getStatusText(sendDebug)));
             return Constants.SUCCESS;
         }));
-    }
 
-    //TODO: change this to a LOG object
-    public static void addDebugLog(String message) {
-        System.out.println("Blade-addons:" + message);
+        Commands.registerDevCommand(ClientCommandManager.literal("toggleSoundInChat").executes(context -> {
+            sendSound = !sendSound;
+            Misc.addChatMessage(Text.literal("Send Sound: ").append(Misc.getStatusText(sendSound)));
+            return Constants.SUCCESS;
+        }));
     }
 
+
+
     public static void sendDebugMessage(Text text) {
         if (sendDebug) {
             Misc.addChatMessage(text);
diff --git a/src/main/java/blade/addon/utils/JsonUtility.java b/src/main/java/blade/addon/utils/JsonUtility.java
index f5387ac..a048687 100644
--- a/src/main/java/blade/addon/utils/JsonUtility.java
+++ b/src/main/java/blade/addon/utils/JsonUtility.java
@@ -32,7 +32,7 @@ public class JsonUtility {
                 return parseSplits(element);
             }
         } catch (IOException e) {
-            Debug.addDebugLog(e.getMessage());
+            Debug.LOGGER.error("Failed to parse a split");
         }
 
         return new HashMap<>();
@@ -78,7 +78,7 @@ public class JsonUtility {
                 return parsePositonalMessages(element);
             }
         } catch (IOException e) {
-            Debug.addDebugLog(e.getMessage());
+            Debug.LOGGER.error("failed to parse positional messages");
         }
 
         return new ArrayList<>();
diff --git a/src/main/java/blade/addon/utils/Keybinds.java b/src/main/java/blade/addon/utils/Keybinds.java
index 22f3f82..cbb5d38 100644
--- a/src/main/java/blade/addon/utils/Keybinds.java
+++ b/src/main/java/blade/addon/utils/Keybinds.java
@@ -1,7 +1,7 @@
 package blade.addon.utils;
 
+import blade.addon.features.item.ItemRarityHighlight;
 import blade.addon.utils.config.Config;
-import blade.addon.utils.dungeon.Phase;
 import net.fabricmc.fabric.api.client.event.lifecycle.v1.ClientTickEvents;
 import net.fabricmc.fabric.api.client.keybinding.v1.KeyBindingHelper;
 import net.minecraft.client.MinecraftClient;
@@ -11,7 +11,6 @@ import net.minecraft.client.util.InputUtil;
 import net.minecraft.component.DataComponentTypes;
 import net.minecraft.component.type.LoreComponent;
 import net.minecraft.item.ItemStack;
-import net.minecraft.nbt.NbtCompound;
 import net.minecraft.text.Text;
 import org.lwjgl.glfw.GLFW;
 
@@ -66,7 +65,7 @@ public class Keybinds {
                 Misc.addChatMessage(line);
             }
 
-            Misc.addChatMessage(Text.literal("Rarity: " + Misc.getRarity(heldStack).name()));
+            Misc.addChatMessage(Text.literal("Rarity: " + ItemRarityHighlight.getRarity(heldStack).name()));
         }
     }
 }
diff --git a/src/main/java/blade/addon/utils/Misc.java b/src/main/java/blade/addon/utils/Misc.java
index 5a06fb7..d23b033 100644
--- a/src/main/java/blade/addon/utils/Misc.java
+++ b/src/main/java/blade/addon/utils/Misc.java
@@ -1,20 +1,19 @@
 package blade.addon.utils;
 
-import blade.addon.features.item.ItemRarity;
+import blade.addon.utils.config.values.ExtraOptions;
 import net.minecraft.client.MinecraftClient;
 import net.minecraft.client.gui.hud.InGameHud;
 import net.minecraft.client.network.ClientPlayNetworkHandler;
 import net.minecraft.client.network.ClientPlayerEntity;
-import net.minecraft.component.DataComponentTypes;
-import net.minecraft.component.type.LoreComponent;
+import net.minecraft.entity.Entity;
 import net.minecraft.entity.player.PlayerEntity;
-import net.minecraft.item.ItemStack;
 import net.minecraft.text.Text;
-
-import java.util.List;
+import net.minecraft.util.Formatting;
 
 public class Misc {
     public static final MinecraftClient instance = MinecraftClient.getInstance();
+    private static final Text ON = Text.literal("ON").formatted(Formatting.GREEN);
+    private static final Text OFF = Text.literal("OFF").formatted(Formatting.RED);
 
     public static boolean isClientPlayer(PlayerEntity entity) {
         ClientPlayerEntity clientPlayer = instance.player;
@@ -22,15 +21,25 @@ public class Misc {
         return clientPlayer == entity;
     }
 
+    public static boolean isClientPlayer(Entity entity) {
+        ClientPlayerEntity clientPlayer = instance.player;
+        if (clientPlayer == null) return false;
+        return clientPlayer == entity;
+    }
+
     public static void addChatMessage(Text text) {
         try {
             InGameHud gameHud = MinecraftClient.getInstance().inGameHud;
-            gameHud.getChatHud().addMessage(text);
+            gameHud.getChatHud(
…… diff 过长已截断 ……
- bumped versionGabriel Ellnebrand · 2025-12-09 · bce9994
查看变更
gradle.properties | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/gradle.properties b/gradle.properties
index 32b236c..af9a288 100644
--- a/gradle.properties
+++ b/gradle.properties
@@ -13,7 +13,7 @@ loader_version=0.17.3
 loom_version=1.13-SNAPSHOT
 
 # Mod Properties
-mod_version=2.2.2
+mod_version=2.2.3
 maven_group=blade.addon
 archives_base_name=blade-addons
 
- fixed skined phoenix not working with InvincibilityTimer (hopefully) - "/ba refill" will only refill items that you already have in your inventory - added countdown option for storm - sends split when over in chat - added item rarity background (not finished) - added terminal splits (colors and core split not finished) - added /leaporder for backup mage (hasn't been fully tested)Gabriel Ellnebrand · 2025-12-09 · 6644b9d
查看变更
src/main/java/blade/addon/Bladeaddons.java                            |  10 +++--
 src/main/java/blade/addon/features/dungeon/LeapOrder.java             | 103 ++++++++++++++++++++++++++++++++++++++++++
 src/main/java/blade/addon/features/dungeon/f7/InvincibilityTimer.java |   6 +--
 src/main/java/blade/addon/features/dungeon/f7/StormTickTimer.java     |  13 ++++++
 src/main/java/blade/addon/features/item/ItemRarity.java               |  26 +++++++++++
 src/main/java/blade/addon/features/item/ItemRarityDrawer.java         |  23 ++++++++++
 src/main/java/blade/addon/features/item/ItemRarityHolder.java         |  14 ++++++
 src/main/java/blade/addon/mixin/HandledScreenMixin.java               |  25 +++++++++++
 src/main/java/blade/addon/mixin/InGameHudMixin.java                   |  23 ++++++++++
 src/main/java/blade/addon/mixin/ItemStackMixin.java                   |  35 +++++++++++++++
 src/main/java/blade/addon/utils/Commands.java                         |  48 +++++++++++++-------
 src/main/java/blade/addon/utils/Constants.java                        |   1 +
 src/main/java/blade/addon/utils/Debug.java                            |  40 ++++++++++++-----
 src/main/java/blade/addon/utils/JsonUtility.java                      |   4 +-
 src/main/java/blade/addon/utils/Keybinds.java                         |  39 +++++++++++++++-
 src/main/java/blade/addon/utils/Location.java                         |   2 -
 src/main/java/blade/addon/utils/Misc.java                             |  37 ++++++++++++++-
 src/main/java/blade/addon/utils/config/Config.java                    |   9 +++-
 src/main/java/blade/addon/utils/config/values/ExtraOptions.java       |   3 ++
 src/main/java/blade/addon/utils/config/values/Floor7.java             |   3 ++
 src/main/java/blade/addon/utils/dungeon/FillHelper.java               |  37 ++++++++++++++-
 src/main/java/blade/addon/utils/dungeon/Phase.java                    |  85 +++++++----------------------------
 src/main/java/blade/addon/utils/dungeon/PositionMessage.java          |   2 +-
 src/main/java/blade/addon/utils/dungeon/Section.java                  | 200 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 src/main/resources/blade-addons.mixins.json                           |   3 ++
 src/main/resources/fabric.mod.json                                    |   4 +-
 26 files changed, 680 insertions(+), 115 deletions(-)
diff --git a/src/main/java/blade/addon/Bladeaddons.java b/src/main/java/blade/addon/Bladeaddons.java
index 395d057..90e88ea 100644
--- a/src/main/java/blade/addon/Bladeaddons.java
+++ b/src/main/java/blade/addon/Bladeaddons.java
@@ -23,13 +23,15 @@ import blade.addon.features.dungeon.f7.RelicTimer;
 import blade.addon.features.dungeon.f7.StormTickTimer;
 import blade.addon.features.dungeon.f7.TermStartTimer;
 import blade.addon.utils.Commands;
+import blade.addon.utils.Debug;
 import blade.addon.utils.Keybinds;
 import blade.addon.utils.Location;
 import blade.addon.utils.Scheduler;
-import blade.addon.utils.config.components.Components;
 import blade.addon.utils.config.Config;
+import blade.addon.utils.config.components.Components;
 import blade.addon.utils.dungeon.DungeonClass;
 import blade.addon.utils.dungeon.Phase;
+import blade.addon.utils.dungeon.Section;
 import net.fabricmc.api.ModInitializer;
 
 public class Bladeaddons implements ModInitializer {
@@ -37,10 +39,12 @@ public class Bladeaddons implements ModInitializer {
 	public void onInitialize() {
         Components.init();
         Config.manager.load();
-		Keybinds.register();
-        Commands.register();
+		Keybinds.init();
+        Commands.init();
+        Debug.init();
 		Location.init();
         Phase.init();
+        Section.init();
         DungeonClass.init();
         StormTickTimer.init();
         GoldorTickTimer.init();
diff --git a/src/main/java/blade/addon/features/dungeon/LeapOrder.java b/src/main/java/blade/addon/features/dungeon/LeapOrder.java
new file mode 100644
index 0000000..526faa3
--- /dev/null
+++ b/src/main/java/blade/addon/features/dungeon/LeapOrder.java
@@ -0,0 +1,103 @@
+package blade.addon.features.dungeon;
+
+import blade.addon.utils.Misc;
+import blade.addon.utils.dungeon.DungeonClass;
+import net.minecraft.client.MinecraftClient;
+import net.minecraft.client.network.ClientPlayerEntity;
+import net.minecraft.scoreboard.Scoreboard;
+import net.minecraft.scoreboard.Team;
+import net.minecraft.text.Text;
+
+import java.util.HashMap;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+public class LeapOrder {
+
+    private static final Pattern PATTERN = Pattern.compile("\\[([TBMAH])] (\\w+) ");
+
+    private static final DungeonClass[] ARCHER_ORDER = {DungeonClass.BERSERK, DungeonClass.HEALER, DungeonClass.MAGE, DungeonClass.TANK};
+    private static final DungeonClass[] BERSERK_ORDER = {DungeonClass.ARCHER, DungeonClass.HEALER, DungeonClass.MAGE, DungeonClass.TANK};
+    private static final DungeonClass[] MAGE_ORDER = {DungeonClass.ARCHER, DungeonClass.BERSERK, DungeonClass.HEALER, DungeonClass.TANK};
+    private static final DungeonClass[] TANK_ORDER = {DungeonClass.TANK, DungeonClass.BERSERK, DungeonClass.HEALER, DungeonClass.MAGE};
+    private static final DungeonClass[] HEALER_ORDER = {DungeonClass.ARCHER, DungeonClass.BERSERK, DungeonClass.MAGE, DungeonClass.TANK};
+
+    private static final DungeonClass[] ALPHABETICAL_ORDER = {DungeonClass.ARCHER, DungeonClass.BERSERK, DungeonClass.HEALER, DungeonClass.MAGE, DungeonClass.TANK};
+
+    public static void leapOrder(String backupMage, boolean odinOrder) {
+
+        HashMap<DungeonClass, String> classNameMap = new HashMap<>();
+        DungeonClass playersClass = null;
+
+        ClientPlayerEntity player = MinecraftClient.getInstance().player;
+        if (player == null) {
+            Misc.addChatMessage(Text.literal("Player is somehow null"));
+            return;
+        }
+
+        Scoreboard scoreboard = player.getScoreboard();
+        for (Team team : scoreboard.getTeams()) {
+            String teamStr = team.getPrefix().getString() + team.getSuffix().getString();
+            Matcher matcher = PATTERN.matcher(teamStr);
+            if (matcher.find()) {
+                DungeonClass dungeonClass = getClass(matcher.group(1));
+                String name = matcher.group(2);
+                if (dungeonClass == null) continue;
+
+                if (name.equalsIgnoreCase(backupMage)) {
+                    dungeonClass = DungeonClass.HEALER;
+                }
+
+                if (name.equalsIgnoreCase(player.getName().getString())) {
+                    playersClass = dungeonClass;
+                    continue;
+                }
+
+               classNameMap.put(dungeonClass, name);
+            }
+        }
+
+        if (classNameMap.size() != 4 || playersClass == null) {
+            Misc.addChatMessage(Text.literal("Cant find all players or the players class"));
+            return;
+        }
+        DungeonClass[] order;
+        if (odinOrder) {
+            order = getOdinOrder(playersClass);
+        } else {
+            order = new DungeonClass[4];
+            int index = 0;
+            for (int i = 0; i < 5; i++) {
+                if (ALPHABETICAL_ORDER[i] == playersClass) continue;
+                order[index] = ALPHABETICAL_ORDER[i];
+                index++;
+            }
+        }
+
+        String leapOrder = "odin leaporder " + classNameMap.get(order[0]) + " " + classNameMap.get(order[1]) + " " + classNameMap.get(order[2]) + " " + classNameMap.get(order[3]);
+        Misc.addChatMessage(Text.literal("Executing command: " + leapOrder));
+        Misc.executeCommand(leapOrder);
+    }
+
+    private static DungeonClass[] getOdinOrder(DungeonClass playerClass) {
+        return switch (playerClass) {
+            case ARCHER -> ARCHER_ORDER;
+            case BERSERK -> BERSERK_ORDER;
+            case HEALER -> HEALER_ORDER;
+            case MAGE -> MAGE_ORDER;
+            case TANK -> TANK_ORDER;
+        };
+    }
+
+    private static DungeonClass getClass(String string) {
+        return switch (string) {
+            case "A" -> DungeonClass.ARCHER;
+            case "B" -> DungeonClass.BERSERK;
+            case "H" -> DungeonClass.HEALER;
+            case "M" -> DungeonClass.MAGE;
+            case "T" -> DungeonClass.TANK;
+            default -> null;
+        };
+    }
+
+}
diff --git a/src/main/java/blade/addon/features/dungeon/f7/InvincibilityTimer.java b/src/main/java/blade/addon/features/dungeon/f7/InvincibilityTimer.java
index 517ac73..bf24432 100644
--- a/src/main/java/blade/addon/features/dungeon/f7/InvincibilityTimer.java
+++ b/src/main/java/blade/addon/features/dungeon/f7/InvincibilityTimer.java
@@ -39,7 +39,7 @@ public class InvincibilityTimer {
     private static final Pattern BONZO_ON_HEAD_PATTERN = Pattern.compile("Bonzo's Mask");
     private static final Pattern SPIRIT_ON_HEAD_PATTERN = Pattern.compile("Spirit Mask");
 
-    private static final Pattern MANUAL_EQUIP_PET_PATTERN = Pattern.compile("^You summoned your (\\D+)!$");
+    private static final Pattern MANUAL_EQUIP_PET_PATTERN = Pattern.compile("^You summoned your (\\D+)( ✦)?!$");
     private static final Pattern RULE_EQUIP_PET_PATTERN = Pattern.compile("^Autopet equipped your \\[Lvl [0-9]+] (\\D+)( ✦)?! VIEW RULE$");
 
     //in ticks
@@ -102,13 +102,13 @@ public class InvincibilityTimer {
         Matcher matcher = MANUAL_EQUIP_PET_PATTERN.matcher(string);
         if (matcher.find()) {
             String petName = matcher.group(1);
-            phoenixOn = petName.equals("Phoenix");
+            phoenixOn = petName.contains("Phoenix");
         }
 
         matcher = RULE_EQUIP_PET_PATTERN.matcher(string);
         if (matcher.find()) {
             String petName = matcher.group(1);
-            phoenixOn = petName.equals("Phoenix");
+            phoenixOn = petName.contains("Phoenix");
         }
     }
 
diff --git a/src/main/java/blade/addon/features/dungeon/f7/StormTickTimer.java b/src/main/java/blade/addon/features/dungeon/f7/StormTickTimer.java
index 60e0460..d89e095 100644
--- a/src/main/java/blade/addon/features/dungeon/f7/StormTickTimer.java
+++ b/src/main/java/blade/addon/features/dungeon/f7/StormTickTimer.java
@@ -25,6 +25,9 @@ public class StormTickTimer {
     private static final long DEATH_DISPLAY_DURATION = 2000;
     private static final int WARN_TICK = 20 * 20;
 
+    private static final int CRUSH_TICK = 31 * 20;
+    private static final int COUNTDOWN_DURATION =  5 * 20;
+
     private static int tick = 0;
     private static double deathTime = 0;
     private static long deathStartDisplayTime = 0;
@@ -65,6 +68,11 @@ public class StormTickTimer {
     }
 
     public static boolean display() {
+        if (Floor7.tickDownStormTickTimer) {
+            double diff = CRUSH_TICK - tick;
+            if (diff > COUNTDOWN_DURATION || diff < 0) return false;
+        }
+
         return Floor7.enableStormTickTimer && Location.inDungeon() && Phase.inP2() && !Phase.stormDead();
     }
 
@@ -74,6 +82,11 @@ public class StormTickTimer {
 
         double num = tick * Constants.TICK_DURATION;
 
+        if (Floor7.tickDownStormTickTimer) {
+            num = CRUSH_TICK * Constants.TICK_DURATION - num;
+
+        }
+
         RenderUtils.drawCenteredText(context, MinecraftClient.getInstance().textRenderer, Constants.DECIMAL_FORMAT.format(num), x, y, component.getWidth());
 
     }
diff --git a/src/main/java/blade/addon/features/item/ItemRarity.java b/src/main/java/blade/addon/features/item/ItemRarity.java
new file mode 100644
index 0000000..ed88c07
--- /dev/null
+++ b/src/main/java/blade/addon/features/item/ItemRarity.java
@@ -0,0 +1,26 @@
+package blade.addon.features.item;
+
+public enum ItemRarity {
+    NONE(0x0),
+    COMMON(0x88ffffff),
+    UNCOMMON(0x8842ab42),
+    RARE(0x884c4cd0),
+    EPIC(0x88671067),
+    LEGENDARY(0x88cf8d0a),
+    MYTHIC(0x88ff55ff),
+    DIVINE(0x884fe4e4),
+    SPECIAL(0x88ff5555),
+    VERY_SPECIAL(0x88c44747),
+    ULTIMATE(0x88a10202),
+    ADMIN(0x88aa0000);
+
+    private int color;
+
+    public int getColor() {
+        return this.color;
+    }
+
+    ItemRarity(int color) {
+        this.color = color;
+    }
+}
diff --git a/src/main/java/blade/addon/features/item/ItemRarityDrawer.java b/src/main/java/blade/addon/features/item/ItemRarityDrawer.java
new file mode 100644
index 0000000..e0d2c1d
--- /dev/null
+++ b/src/main/java/blade/addon/features/item/ItemRarityDrawer.java
@@ -0,0 +1,23 @@
+package blade.addon.features.item;
+
+import blade.addon.utils.Misc;
+import net.minecraft.client.gui.DrawContext;
+import net.minecraft.item.ItemStack;
+
+public class ItemRarityDrawer {
+
+    public static void draw(DrawContext context, ItemStack stack, int x, int y) {
+        ItemRarityHolder holder = (ItemRarityHolder) (Object) stack;
+        assert holder != null;
+
+        if (!holder.blade_addons$hasScanned()) {
+            ItemRarity rarity = Misc.getRarity(stack);
+            holder.blade_addons$setItemRarity(rarity);
+        }
+
+        if (!holder.blade_addons$hasItemRarity()) return;
+
+        ItemRarity rarity = holder.blade_addons$getItemRarity();
+        context.fill(x, y, x + 16, y + 16, rarity.getColor());
+    }
+}
diff --git a/src/main/java/blade/addon/features/item/ItemRarityHolder.java b/src/main/java/blade/addon/features/item/ItemRarityHolder.java
new file mode 100644
index 0000000..8ebebf8
--- /dev/null
+++ b/src/main/java/blade/addon/features/item/ItemRarityHolder.java
@@ -0,0 +1,14 @@
+package blade.addon.features.item;
+
+public interface ItemRarityHolder {
+
+    ItemRarity itemRarity = ItemRarity.NONE;
+
+    ItemRarity blade_addons$getItemRarity();
+
+    boolean blade_addons$hasItemRarity();
+
+    void blade_addons$setItemRarity(ItemRarity itemRarity);
+
+    boolean blade_addons$hasScanned();
+}
diff --git a/src/main/java/blade/addon/mixin/HandledScreenMixin.java b/src/main/java/blade/addon/mixin/HandledScreenMixin.java
new file mode 100644
index 0000000..740c93f
--- /dev/null
+++ b/src/main/java/blade/addon/mixin/HandledScreenMixin.java
@@ -0,0 +1,25 @@
+package blade.addon.mixin;
+
+import blade.addon.features.item.ItemRarityDrawer;
+import blade.addon.utils.config.values.ExtraOptions;
+import net.minecraft.client.gui.DrawContext;
+import net.minecraft.client.gui.screen.ingame.HandledScreen;
+import net.minecraft.item.ItemStack;
+import net.minecraft.screen.slot.Slot;
+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;
+
+@Mixin(HandledScreen.class)
+public class HandledScreenMixin {
+
+    @Inject(method = "drawSlot", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/gui/DrawContext;drawItem(Lnet/minecraft/item/ItemStack;III)V"))
+    public void drawBackground(DrawContext context, Slot slot, CallbackInfo ci) {
+        if (!ExtraOptions.itemRarityBackground) return;
+        int x = slot.x;
+        int y = slot.y;
+        ItemStack stack = slot.getStack();
+        ItemRarityDrawer.draw(context, stack, x, y);
+    }
+}
diff --git a/src/main/java/blade/addon/mixin/InGameHudMixin.java b/src/main/java/blade/addon/mixin/InGameHudMixin.java
new file mode 100644
index 0000000..bc0c073
--- /dev/null
+++ b/src/main/java/blade/addon/mixin/InGameHudMixin.java
@@ -0,0 +1,23 @@
+package blade.addon.mixin;
+
+import blade.addon.features.item.ItemRarityDrawer;
+import blade.addon.utils.config.values.ExtraOptions;
+import net.minecraft.client.gui.DrawContext;
+import net.minecraft.client.gui.hud.InGameHud;
+import net.minecraft.client.render.RenderTickCounter;
+import net.minecraft.entity.player.PlayerEntity;
+import net.minecraft.item.ItemStack;
+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;
+
+@Mixin(InGameHud.class)
+public class InGameHudMixin {
+
+    @Inject(method = "renderHotbarItem", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/gui/DrawContext;drawItem(Lnet/minecraft/entity/LivingEntity;Lnet/minecraft/item/ItemStack;III)V"))
+    public void drawItem(DrawContext context, int x, int y, RenderTickCounter tickCounter, PlayerEntity player, ItemStack stack, int seed, CallbackInfo ci) {
+        if (!ExtraOptions.itemRarityBackground) return;
+        ItemRarityDrawer.draw(context, stack, x, y);
+    }
+}
diff --git a/src/main/java/blade/addon/mixin/ItemStackMixin.java b/src/main/java/blade/addon/mixin/ItemStackMixin.java
new file mode 100644
index 0000000..0e5c0c1
--- /dev/null
+++ b/src/main/java/blade/addon/mixin/ItemStackMixin.java
@@ -0,0 +1,35 @@
+package blade.addon.mixin;
+
+import blade.addon.features.item.ItemRarityHolder;
+import blade.addon.features.item.ItemRarity;
+import net.minecraft.item.ItemStack;
+import org.spongepowered.asm.mixin.Mixin;
+import org.spongepowered.asm.mixin.Unique;
+
+@Mixin(ItemStack.class)
+public class ItemStackMixin implements ItemRarityHolder {
+
+    @Unique
+    private ItemRarity itemRarity = null;
+
+
+    @Override
+    public ItemRarity blade_addons$getItemRarity() {
+        return itemRarity;
+    }
+
+    @Override
+    public boolean blade_addons$hasItemRarity() {
+        return itemRarity != ItemRarity.NONE;
+    }
+
+    @Override
+    public void blade_addons$setItemRarity(ItemRarity itemRarity) {
+        this.itemRarity = itemRarity;
+    }
+
+    @Override
+    public boolean blade_addons$hasScanned() {
+        return itemRarity != null;
+    }
+}
diff --git a/src/main/java/blade/addon/utils/Commands.java b/src/main/java/blade/addon/utils/Commands.java
index b93c22d..65b2ed0 100644
--- a/src/main/java/blade/addon/utils/Commands.java
+++ b/src/main/java/blade/addon/utils/Commands.java
@@ -1,11 +1,14 @@
 package blade.addon.utils;
 
+import blade.addon.features.dungeon.LeapOrder;
 import blade.addon.features.dungeon.f7.BossWaypoints;
 import blade.addon.utils.config.Config;
 import blade.addon.utils.dungeon.FillHelper;
 import com.mojang.brigadier.CommandDispatcher;
+import com.mojang.brigadier.arguments.BoolArgumentType;
 import com.mojang.brigadier.arguments.DoubleArgumentType;
 import com.mojang.brigadier.arguments.StringArgumentType;
+import com.mojang.brigadier.builder.LiteralArgumentBuilder;
 import net.fabricmc.fabric.api.client.command.v2.ClientCommandManager;
 import net.fabricmc.fabric.api.client.command.v2.ClientCommandRegistrationCallback;
 import net.fabricmc.fabric.api.client.command.v2.FabricClientCommandSource;
@@ -14,38 +17,56 @@ import net.minecraft.client.network.ClientPlayerEntity;
 import net.minecraft.command.CommandRegistryAccess;
 import org.jetbrains.annotations.NotNull;
 
+import java.util.ArrayList;
+
 public class Commands {
 
     private static final String[] COMMAND_ALIASES = {"ba", "blade", "bladeaddons"};
+    private static final LiteralArgumentBuilder<FabricClientCommandSource> devRoot = ClientCommandManager.literal("badev");
 
-    public static void register() {
+    public static void init() {
         ClientCommandRegistrationCallback.EVENT.register(Commands::registerCommands);
     }
 
     public static void registerCommands(@NotNull CommandDispatcher<FabricClientCommandSource> dispatcher,
                                         CommandRegistryAccess registryAccess) {
 
+        dispatcher.register(devRoot);
+
+
         //prob not a good solution but I don't use that many commands currently
         for (String alias : COMMAND_ALIASES) {
             dispatcher.register(
                     ClientCommandManager.literal(alias)
-                            .then(ClientCommandManager.literal("ep").executes(context -> FillHelper.fillItem(MinecraftClient.getInstance(), FillHelper.ENDER_PEARL, 16, 16)))
+                            .then(ClientCommandManager.literal("ep").executes(context -> FillHelper.fillItem(FillHelper.ENDER_PEARL, 16, 16, false)))
 
-                            .then(ClientCommandManager.literal("sb").executes(context -> FillHelper.fillItem(MinecraftClient.getInstance(), FillHelper.SUPERBOOM_TNT, 64, 64)))
+                            .then(ClientCommandManager.literal("sb").executes(context -> FillHelper.fillItem(FillHelper.SUPERBOOM_TNT, 64, 64, false)))
 
-                            .then(ClientCommandManager.literal("ij").executes(context -> FillHelper.fillItem(MinecraftClient.getInstance(), FillHelper.INFLATABLE_JERRY, 64, 64)))
+                            .then(ClientCommandManager.literal("ij").executes(context -> FillHelper.fillItem(FillHelper.INFLATABLE_JERRY, 64, 64, false)))
 
                             .then(ClientCommandManager.literal("refill")
                                     .executes(
                                             context -> {
-                                                FillHelper.fillItem(MinecraftClient.getInstance(), FillHelper.ENDER_PEARL, 16, 16);
-                                                Scheduler.scheduleTask(() -> FillHelper.fillItem(MinecraftClient.getInstance(), FillHelper.SUPERBOOM_TNT, 64, 64), 40);
-                                                Scheduler.scheduleTask(() -> FillHelper.fillItem(MinecraftClient.getInstance(), FillHelper.INFLATABLE_JERRY, 64, 64), 80);
+                                                FillHelper.fillItem(FillHelper.ENDER_PEARL, 16, 16, true);
+                                                Scheduler.scheduleTask(() -> FillHelper.fillItem(FillHelper.SUPERBOOM_TNT, 64, 64, true), 40);
+                                                Scheduler.scheduleTask(() -> FillHelper.fillItem(FillHelper.INFLATABLE_JERRY, 64, 64, true), 80);
                                                 return Constants.SUCCESS;
                                             }
 
                                     ))
 
+                            .then(ClientCommandManager.literal("leaporder")
+                                    .then(ClientCommandManager.argument("backupMage", StringArgumentType.string())
+                                            .then(ClientCommandManager.argument("odinOrder", BoolArgumentType.bool())
+                                                    .executes(context -> {
+                                                        String backupMage = StringArgumentType.getString(context, "backupMage");
+                                                        boolean odinOrder = BoolArgumentType.getBool(context, "odinOrder");
+                                                        LeapOrder.leapOrder(backupMage, odinOrder);
+                                                        return Constants.SUCCESS;
+                                                    })
+                                            )
+                                    )
+                            )
 
                             .then(ClientCommandManager.literal("waypoint")
 
@@ -145,16 +166,6 @@ public class Commands {
 
                             )
 
-                            .then(ClientCommandManager.literal("debug")
-                                    .then(ClientCommandManager.argument("option", StringArgumentType.greedyString()).executes(context -> {
-                                                String option = StringArgumentType.getString(context, "option");
-                                                return Debug.parseOption(option);
-                                            })
-                                    )
-                            )
-
-
-
                             .executes(commandContext -> Scheduler.scheduleScreen(Config.createScreen(null)))
             );
 
@@ -165,4 +176,7 @@ public class Commands {
         }
     }
 
+    public static void registerDevCommand(LiteralArgumentBuilder<FabricClientCommandSource> commandManager) {
+        devRoot.then(commandManager);
+    }
 }
diff --git a/src/main/java/blade/addon/utils/Constants.java b/src/main/java/blade/addon/utils/Constants.java
index a94d879..c1931b5 100644
--- a/src/main/java/blade/addon/utils/Constants.java
+++ b/src/main/java/blade/addon/utils/Constants.java
@@ -6,6 +6,7 @@ public class Constants {
     public static final String NAMESPACE = "blade-addons";
     public static final DecimalFormat DECIMAL_FORMAT = new DecimalFormat("0.00");
     public static final double TICK_DURATION = 0.05;
+    public static final int TEXT_HEIGHT = 10;
 
     public static final int GREEN_COLOR = 0xff00ff00;
     public static final int ORANGE_COLOR = 0xffDAA06D;
diff --git a/src/main/java/blade/addon/utils/Debug.java b/src/main/java/blade/addon/utils/Debug.java
index 35064f0..7d348cd 100644
--- a/src/main/java/blade/addon/utils/Debug.java
+++ b/src/main/java/blade/addon/utils/Debug.java
@@ -1,27 +1,47 @@
 package blade.addon.utils;
 
 import blade.addon.utils.dungeon.Phase;
+import blade.addon.utils.dungeon.Section;
+import net.fabricmc.fabric.api.client.command.v2.ClientCommandManager;
 import net.minecraft.text.Text;
 
 public class Debug {
 
-    public static int parseOption(String string) {
+    private static boolean sendDebug = false;
 
-        switch (string) {
-            case "RUN_INFO":
-                sendRunInfo();
-                break;
-        }
+    public static void init() {
+       registerCommands();
+
+    }
 
+    private static void registerCommands() {
+        Commands.registerDevCommand(ClientCommandManager.literal("runInfo").executes(context -> {
+            sendRunInfo();
+            return Constants.SUCCESS;
+        }));
+
+        Commands.registerDevCommand(ClientCommandManager.literal("toggleSendDebug").executes(context -> {
+            sendDebug = !sendDebug;
+            Misc.addChatMessage(Text.literal("Send debug: " + sendDebug));
+            return Constants.SUCCESS;
+        }));
+    }
 
-        return Constants.SUCCESS;
+    //TODO: change this to a LOG object
+    public static void addDebugLog(String message) {
+        System.out.println("Blade-addons:" + message);
+    }
+
+    public static void sendDebugMessage(Text text) {
+        if (sendDebug) {
+            Misc.addChatMessage(text);
+        }
     }
 
     public static void sendRunInfo() {
         Misc.addChatMessage(Text.literal("Phase: " + Phase.getPhase()));
-        Misc.addChatMessage(Text.literal("Section: " + Phase.getSection()));
-        Misc.addChatMessage(Text.literal("Gateblown: " + Phase.isGateBlownUp()));
-        Misc.addChatMessage(Text.literal("Terms done: " + Phase.isTermsDone()));
+        Misc.addChatMessage(Text.literal("Section: " + Section.getSection()));
+        Misc.addChatMessage(Text.literal("Gateblown: " + Section.isGateBlownUp()));
         Misc.addChatMessage(Text.literal("In floor 7: " + Phase.isInFloor7()));
     }
 
diff --git a/src/main/java/blade/addon/utils/JsonUtility.java b/src/main/java/blade/addon/utils/JsonUtility.java
index 78118b4..f5387ac 100644
--- a/src/main/java/blade/addon/utils/JsonUtility.java
+++ b/src/main/java/blade/addon/utils/JsonUtility.java
@@ -32,7 +32,7 @@ public class JsonUtility {
                 return parseSplits(element);
             }
         } catch (IOException e) {
-            System.out.println(e.getMessage());
+            Debug.addDebugLog(e.getMessage());
         }
 
         return new HashMap<>();
@@ -78,7 +78,7 @@ public class JsonUtility {
                 return parsePositonalMessages(element);
             }
         } catch (IOException e) {
-            System.out.println(e.getMessage());
+            Debug.addDebugLog(e.getMessage());
         }
 
         return new ArrayList<>();
diff --git a/src/main/java/blade/addon/utils/Keybinds.java b/src/main/java/blade/addon/utils/Keybinds.java
index 2bae426..22f3f82 100644
--- a/src/main/java/blade/addon/utils/Keybinds.java
+++ b/src/main/java/blade/addon/utils/Keybinds.java
@@ -5,17 +5,26 @@ import blade.addon.utils.dungeon.Phase;
 import net.fabricmc.fabric.api.client.event.lifecycle.v1.ClientTickEvents;
 import net.fabricmc.fabric.api.client.keybinding.v1.KeyBindingHelper;
 import net.minecraft.client.MinecraftClient;
+import net.minecraft.client.network.ClientPlayerEntity;
 import net.minecraft.client.option.KeyBinding;
 import net.minecraft.client.util.InputUtil;
+import net.minecraft.component.DataComponentTypes;
+import net.minecraft.component.type.LoreComponent;
+import net.minecraft.item.ItemStack;
+import net.minecraft.nbt.NbtCompound;
+import net.minecraft.text.Text;
 import org.lwjgl.glfw.GLFW;
 
+import java.util.List;
+
 public class Keybinds {
 
     private static final String CATEGORY = Constants.NAMESPACE;
 
     private static KeyBinding openConfig;
+    private static KeyBinding getItemLore;
 
-    public static void register() {
+    public static void init() {
 
         openConfig = KeyBindingHelper.registerKeyBinding(new KeyBinding(
                 "opens Config",
@@ -23,6 +32,12 @@ public class Keybinds {
                 GLFW.GLFW_KEY_RIGHT_SHIFT,
                 CATEGORY));
 
+        getItemLore = KeyBindingHelper.registerKeyBinding(new KeyBinding(
+                "Grabs the items lore",
+                InputUtil.Type.KEYSYM,
+                GLFW.GLFW_KEY_UNKNOWN,
+                CATEGORY));
+
         ClientTickEvents.END_CLIENT_TICK.register(Keybinds::checkInputs);
     }
 
@@ -31,5 +46,27 @@ public class Keybinds {
         if (openConfig.wasPressed()) {
             client.setScreen(Config.createScreen(null));
         }
+
+        if (getItemLore.wasPressed()) {
+            ClientPlayerEntity player = client.player;
+            if (player == null) {
+                Misc.addChatMessage(Text.literal("player is null"));
+                return;
+            }
+
+            ItemStack heldStack = player.getMainHandStack();
+            LoreComponent lore = heldStack.get(DataComponentTypes.LORE);
+            if (lore == null) {
+                Misc.addChatMessage(Text.literal("lore is null"));
+                return;
+            }
+
+            List<Text> lines = lore.lines();
+            for(Text line: lines) {
+                Misc.addChatMessage(line);
+            }
+
+            Misc.addChatMessage(Text.literal("Rarity: " + Misc.getRarity(heldStack).name()));
+        }
     }
 }
diff --git a/src/main/java/blade/addon/utils/Location.java b/src/main/java/blade/addon/utils/Location.java
index d371b13..10aa288 100644
--- a/src/main/java/blade/addon/utils/Location.java
+++ b/src/main/java/blade/addon/utils/Location.java
@@ -25,8 +25,6 @@ public enum Location {
                 inSkyblock = serverType.getName().equals("SkyBlock");
             }
 
-            System.out.println(map.toUpperCase().replace(" ", "_"));
-
             try {
                 currentLocation = Location.valueOf(map.toUpperCase().replace(" ", "_"));
             } catch (IllegalArgumentException ignored) {
diff --git a/src/main/java/blade/addon/utils/Misc.java b/src/main/java/blade/addon/utils/Misc.java
index 3035c64..5a06fb7 100644
--- a/src/main/java/blade/addon/utils/Misc.java
+++ b/src/main/java/blade/addon/utils/Misc.java
@@ -1,12 +1,18 @@
 package blade.addon.utils;
 
+import blade.addon.features.item.ItemRarity;
 import net.minecraft.client.MinecraftClient;
 import net.minecraft.client.gui.hud.InGameHud;
 import net.minecraft.client.network.ClientPlayNetworkHandler;
 import net.minecraft.client.network.ClientPlayerEntity;
+import net.minecraft.component.DataComponentTypes;
+import net.minecraft.component.type.LoreComponent;
 import net.minecraft.entity.player.PlayerEntity;
+import net.minecraft.item.ItemStack;
 import net.minecraft.text.Text;
 
+import java.util.List;
+
 public class Misc {
     public static final MinecraftClient instance = MinecraftClient.getInstance();
 
@@ -17,8 +23,12 @@ public class Misc {
     }
 
     public static void addChatMessage(Text text) {
-        InGameHud gameHud = MinecraftClient.getInstance().inGameHud;
-        gameHud.getChatHud().addMessage(text);
+        try {
+            InGameHud gameHud = MinecraftClient.getInstance().inGameHud;
+            gameHud.getChatHud().addMessage(text);
+        } catch (IndexOutOfBoundsException ignored) {
+            Debug.addDebugLog("Chat message failed to get added");
+        }
     }
 
     public static void setTitle(Text text) {
@@ -30,4 +40,27 @@ public class Misc {
         if (networkHandler == null) return;
         networkHandler.sendChatCommand(string);
     }
+
+    public static ItemRarity getRarity(ItemStack item) {
+        LoreComponent lore = item.get(DataComponentTypes.LORE);
+        if (lore == null)  return ItemRarity.NONE;
+
+        List<Text> lines = lore.lines();
+        if (lines.isEmpty()) return ItemRarity.NONE;
+        Text line = lines.getLast();
+
+        if (line == null) return ItemRarity.NONE;
+        String string = line.getString();
+        String[] rarityStrings = string.split(" ");
+
+        for (String testString: rarityStrings) {
+            try {
+               ItemRarity rarity = ItemRarity.valueOf(testString);
+               return rarity;
+            } catch (IllegalArgumentException ignored) {
+
+            }
+        }
+        return ItemRarity.NONE;
+    }
 }
diff --git a/src/main/java/blade/addon/utils/config/Config.java b/src/main/java/blade/addon/utils/config/Config.java
index 6bdd30a..118037d 100644
--- a/src/main/java/blade/addon/utils/config/Config.java
+++ b/src/main/java/blade/addon/utils/config/Config.java
@@ -10,6 +10,7 @@ import blade.addon.utils.config.values.Dungeons;
 import blade.addon.utils.config.values.ExtraOptions;
 import blade.addon.utils.config.values.Floor7;
 import blade.addon.utils.dungeon.Phase;
+import blade.addon.utils.dungeon.Section;
 import blade.addon.utils.dungeon.Split;
 import config.practical.ConfigurableScreen;
 import config.practical.category.ConfigCategory;
@@ -29,7 +30,7 @@ public class Config {
 
     private static final Text TITLE = Text.literal("Blade Addons");
     public static final ConfigManager manager = new ConfigManager("./config/" + Constants.NAMESPACE + ".json",
-            List.of(Phase.class, Split.class, MobHighlight.class, ExtraOptions.class, DianaNotifier.class, Components.class, Dungeons.class, Floor7.class));
+            List.of(Phase.class, Section.class, Split.class, MobHighlight.class, ExtraOptions.class, DianaNotifier.class, Components.class, Dungeons.class, Floor7.class));
 
     public static Screen createScreen(Screen parent) {
         ConfigurableScreen screen = new ConfigurableScreen(TITLE, parent, manager);
@@ -91,6 +92,7 @@ public class Config {
 
         ConfigSection storm = new ConfigSection(Text.literal("Storm"));
         storm.add(new ConfigBool(Text.literal("Storm Tick timer"), () -> Floor7.enableStormTickTimer, bool -> Floor7.enableStormTickTimer = bool));
+        storm.add(new ConfigBool(Text.literal("Tick down from 5"), () -> Floor7.tickDownStormTickTimer, bool -> Floor7.tickDownStormTickTimer = bool));
         storm.add(new ConfigBool(Text.literal("First Death time"), () -> Floor7.enableStormDeathTime, bool -> Floor7.enableStormDeathTime = bool));
         storm.add(new ConfigBool(Text.literal("Distance to ledge"), () -> Floor7.displayDistanceToLedge, bool -> Floor7.displayDistanceToLedge = bool));
         storm.add(new ConfigBool(Text.literal("Warn if spirit mask is used"), () -> Floor7.notifyUsedSpiritMask, bool -> Floor7.notifyUsedSpiritMask = bool));
@@ -101,6 +103,9 @@ public class Config {
         goldor.add(new ConfigBool(Text.literal("Term start time"), () -> Floor7.enableTermStartTimer, bool -> Floor7.enableTermStartTimer = bool));
         goldor.add(new ConfigBool(Text.literal("death ticks intervals"), () -> Floor7.inDeathTicks, bool -> Floor7.inDeathTicks = bool));
         goldor.add(new ConfigBool(Text.literal("Positional messages"), () -> Floor7.enablePositionalMessages, bool -> Floor7.enablePositionalMessages = bool));
+        goldor.add(new ConfigBool(Text.literal("Terminal splits"), () -> Section.enableTerminalSplits, bool -> Section.enableTerminalSplits = bool));
+        goldor.add(new ConfigOptions<>(Text.literal("Display when"), Section.DisplayTerminalSplitsWhen.values(), () -> Section.displayTerminalSplitsWhen, when -> Section.displayTerminalSplitsWhen = when));
+
 
         floor7.add(goldor);
 
@@ -116,6 +121,7 @@ public class Config {
         ConfigCategory splits = new ConfigCategory("Splits");
         splits.add(new ConfigBool(Text.literal("Enable Splits"), () -> Phase.enableSplits, bool -> Phase.enableSplits = bool));
         splits.add(new ConfigBool(Text.literal("Include total time"), () -> Phase.includeTotalTime, bool -> Phase.includeTotalTime = bool));
+        splits.add(new ConfigBool(Text.literal("Send split in chat when over"), () -> Phase.sendSplitInChat, bool -> Phase.sendSplitInChat = bool));
         splits.add(new ConfigOptions<>(Text.literal("Tick timer type"), Split.TimerType.values(), () -> Split.timerType, type -> Split.timerType = type));
 
         splits.add(new ConfigColor(Text.literal("Real time color (Inactive)"), () -> Split.realTimeColorInactive, color -> Split.realTimeColorInactive = color, "real-time-inactive", false));
@@ -160,6 +166,7 @@ public class Config {
         extra.add(new ConfigBool(Text.literal("Hide arrows stuck to entities"), () -> ExtraOptions.hideStuckArrows, bool -> ExtraOptions.hideStuckArrows = bool));
         extra.add(new ConfigBool(Text.literal("Disable ability on cooldown sound"), () -> ExtraOptions.disableAbilityCooldownSound, bool -> ExtraOptions.disableAbilityCooldownSound = bool));
         extra.add(new ConfigBool(Text.literal("Hide dead entities"), () -> ExtraOptions.hideDeadEntities, bool -> ExtraOptions.hideDeadEntities = bool));
+        extra.add(new ConfigBool(Text.literal("Item rarity background"), () -> ExtraOptions.itemRarityBackground, bool -> ExtraOptions.itemRarityBackground = bool));
 
         ConfigSection diana = new ConfigSection(Text.literal("Diana notifications"));
         diana.add(new ConfigBool(Text.literal("Send Sound"), () -> DianaNotifier.sendSound, bool -> DianaNotifier.sendSound = bool));
diff --git a/src/main/java/blade/addon/utils/config/values/ExtraOptions.java b/src/main/java/blade/addon/utils/config/values/ExtraOptions.java
index fcc3e14..8ad4bc7 100644
--- a/src/main/java/blade/addon/utils/config/values/ExtraOptions.java
+++ b/src/main/java/blade/addon/utils/config/values/ExtraOptions.java
@@ -14,4 +14,7 @@ public class ExtraOptions {
 
     @ConfigValue
     public static boolean hideDeadEntities = false;
+
+    @ConfigValue
+    public static boolean itemRarityBackground = false;
 }
diff --git a/src/main/java/blade/addon/utils/config/values/Floor7.java b/src/main/java/blade/addon/utils/config/values/Floor7.java
index ca96546..d303a22 100644
--- a/src/main/java/blade/addon/utils/config/values/Floor7.java
+++ b/src/main/java/blade/addon/utils/config/values/Floor7.java
@@ -19,6 +19,9 @@ public class Floor7 {
     @ConfigValue
     public static boolean enableStormTickTimer = false;
 
+    @ConfigValue
+    public static boolean tickDownStormTickTimer = false;
+
     @ConfigValue
     public static boolean enableStormDeathTime = false;
 
diff --git a/src/main/java/blade/addon/utils/dungeon/FillHelper.java b/src/main/java/blade/addon/utils/dungeon/FillHelper.java
index bf2d886..6279717 100644
--- a/src/main/java/blade/addon/utils/dungeon/FillHelper.java
+++ b/src/main/java/blade/addon/utils/dungeon/FillHelper.java
@@ -11,14 +11,47 @@ public class FillHelper {
     public static final String ENDER_PEARL = "Ender Pearl";
     public static final String SUPERBOOM_TNT = "Superboom TNT";
     public static final String INFLATABLE_JERRY = "Inflatable Jerry";
+    public static final String DECOY = "Decoy";
 
     /**
-     * @param client to get access to the player and networkHandler
      * @param itemName Name of item
      * @param minThreshold an integer that the item count has to be lower than to fill
      * @param maxCount the max count of the item
      * @return 1 if it succeeds else 0 for fail
      */
+    public static int fillItem(String itemName, int minThreshold, int maxCount, boolean needAtleastOne) {
+        ClientPlayerEntity player = MinecraftClient.getInstance().player;
+
+        if (player == null) return Constants.FAIL;
+
+        Inventory inventory = player.getInventory();
+
+        int currentCount = 0;
+
+        for (int i = 0; i < inventory.size(); i++) {
+
+            ItemStack item = inventory.getStack(i);
+
+            if (item.getName().toString().contains(itemName)) {
+
+                int highestCount = item.getCount();
+
+                if (highestCount > currentCount) {
+                    currentCount = highestCount;
+
+                    if (highestCount >= maxCount) return Constants.FAIL;
+                }
+            }
+
+        }
+
+        if ((needAtleastOne && currentCount == 0) || minThreshold < currentCount) return Constants.FAIL;
+
+        int itemsToGive = maxCount - currentCount;
+        player.networkHandler.sendChatCommand("gfs " + itemName + " " + itemsToGive);
+        return Constants.SUCCESS;
+    }
+
     public static int fillItem(MinecraftClient client, String itemName, int minThreshold, int maxCount) {
         ClientPlayerEntity player = client.player;
 
@@ -45,7 +78,7 @@ public class FillHelper {
 
         }
 
-        if (minThreshold < currentCount) return Constants.FAIL;
+        if ( minThreshold < currentCount) return Constants.FAIL;
 
         int itemsToGive = maxCount - currentCount;
         player.networkHandler.sendChatCommand("gfs " + itemName + " " + itemsToGive);
diff --git a/src/main/java/blade/addon/utils/dungeon/Phase.java b/src/main/java/blade/addon/utils/dungeon/Phase.java
index ac42754..de9d773 100644
--- a/src/main/java/blade/addon/utils/dungeon/Phase.java
+++ b/src/main/java/blade/addon/utils/dungeon/Phase.java
@@ -23,23 +23,17 @@ public class Phase {
 
     private static final Pattern END_PATTERN = Pattern.compile("^\\s*☠ Defeated (.+) in 0?([\\dhms ]+)\\s*(\\(NEW RECORD!\\))?$");
     private static final Pattern SEARCH_PATTERN = Pattern.compile("^ ⏣ The Catacombs .*$");
-    private static final Pattern TERMINALS_DONE_PATTERN = Pattern.compile("(activated|completed) (a terminal|a device|a lever)! \\((\\d)/(\\d)\\)$");
 
     private static final Split DUMMY_SPLIT = new Split("test split", "if this is called idk", "if this is called idk", 43690);
 
     private static final HashMap<String, ArrayList<Split>> FLOOR_SPLITS = JsonUtility.readSplits("/data/splits.json");
 
-    private static final int TEXT_HEIGHT = 10;
     private static final int DUMMY_SIZE = 10;
 
     private static ArrayList<Split> currentSplits;
     private static int currentPhase = -1;
     private static String floor = "";
 
-    private static int currentSection = 0;
-    private static boolean termsDone = false;
-    private static boolean gateBlownUp = false;
-
     private static boolean inFloor7 = false;
     private static boolean stormDead = false;
     private static boolean runOver = false;
@@ -50,6 +44,9 @@ public class Phase {
     @ConfigValue
     public static boolean includeTotalTime = false;
 
+    @ConfigValue
+    public static boolean sendSplitInChat = false;
+
     public static void init() {
         Events.ON_SERVER_TICK.register(() -> {
             if (currentSplits == null || runOver) return;
@@ -95,9 +92,6 @@ public class Phase {
         inFloor7 = false;
         stormDead = false;
         runOver = false;
-        currentSection = 0;
-        termsDone = false;
-        gateBlownUp = false;
     }
 
     public static void parseGameMessage(Text message) {
@@ -114,6 +108,10 @@ public class Phase {
             currentSplit.parseMessage(string);
 
             if (currentSplit.ended()) {
+                if (sendSplitInChat) {
+                    Misc.addChatMessage(currentSplit.createNameText().append(currentSplit.createTimeText()));
+                }
+
                 currentPhase = i + 1;
                 if (Events.ON_PHASE_CHANGE.hasListeners()) {
                     Events.ON_PHASE_CHANGE.invoke(PhaseEvent::onPhaseChange);
@@ -134,11 +132,9 @@ public class Phase {
         }
 
         if (inP2()) {
-            parseP2(string);
-        }
-
-        if (inP3()) {
-            parseP3(string);
+            if (string.equals("[BOSS] Storm: I should have known that I stood no chance.")) {
+                stormDead = true;
+            }
         }
     }
 
@@ -163,64 +159,15 @@ public class Phase {
         }
     }
 
-
-    private static void parseP2(String string) {
-        if (string.equals("[BOSS] Storm: I should have known that I stood no chance.")) {
-            stormDead = true;
-            currentSection = 1;
-        }
-    }
-
-    private static void parseP3(String string) {
-        if (!termsDone) {
-            Matcher matcher = TERMINALS_DONE_PATTERN.matcher(string);
-            if (matcher.find()) {
-                String num1 = matcher.group(3);
-                String num2 = matcher.group(4);
-                if (num1.equals(num2)) {
-                    termsDone = true;
-                }
-            }
-        }
-
-        if (!gateBlownUp) {
-            if (string.equals("The gate has been destroyed!")) {
-                gateBlownUp = true;
-            }
-        }
-
-        if (gateBlownUp && termsDone) {
-            currentSection++;
-            gateBlownUp = false;
-            termsDone = false;
-        }
-
-        if (string.equals("The Core entrance is opening!")) {
-            //so in "goldor tunnel" can be shown after terms are done
-            currentSection = 5;
-        }
-    }
-
     public static double getPhase() {
         return currentPhase;
     }
 
-    public static double getSection() {
-        return currentSection;
-    }
 
     public static boolean isInFloor7() {
         return inFloor7;
     }
 
-    public static boolean isGateBlownUp() {
-        return gateBlownUp;
-    }
-
-    public static boolean isTermsDone() {
-        return termsDone;
-    }
-
     public static boolean runStarted() {
         return currentPhase >= 0;
     }
@@ -245,12 +192,12 @@ public class Phase {
         return currentPhase == 6 && inFloor7;
     }
 
-    public static boolean inP3() {
-        return (currentPhase == 6 || currentPhase == 7) && inFloor7;
+    public static boolean inGoldorTunnel() {
+        return currentPhase == 7 && inFloor7;
     }
 
-    public static boolean inSection(int section) {
-        return currentSection == section && inP3();
+    public static boolean inP3() {
+        return (currentPhase == 6 || currentPhase == 7) && inFloor7;
     }
 
     public static boolean inP5() {
@@ -275,12 +222,12 @@ public class Phase {
                     if (!includeTotalTime) splitCount--;
 
                     for (int i = 0; i < splitCount; i++) {
-                        currentSplits.get(i).drawSplit(drawContext, textRenderer, x, y + TEXT_HEIGHT * i);
+                        currentSplits.get(i).drawSplit(drawContext, textRenderer, x, y + Constants.TEXT_HEIGHT * i);
 
                     }
                 } else {
                     for (int i = 0; i < DUMMY_SIZE; i++) {
-                        DUMMY_SPLIT.drawSplit(drawContext, textRenderer, x, y + TEXT_HEIGHT * i);
+                        DUMMY_SPLIT.drawSplit(drawContext, textRenderer, x, y + Constants.TEXT_HEIGHT * i);
                     }
                 }
             }), () -> enableSplits
diff --git a/src/main/java/blade/addon/utils/dungeon/PositionMessage.java b/src/main/java/blade/addon/utils/dungeon/PositionMessage.java
index 6c9affd..767a951 100644
--- a/src/main/java/blade/addon/utils/dungeon/PositionMessage.java
+++ b/src/main/java/blade/addon/utils/dungeon/PositionMessage.java
@@ -26,7 +26,7 @@ public class PositionMessage {
 
     private boolean inAValidSection() {
         for (int section: sections) {
-            if (Phase.inSection(section)) return true;
+            if (Section.inSection(section)) return true;
         }
         return false;
     }
diff --git a/src/main/java/blade/addon/utils/dungeon/Section.java b/src/main/java/blade/addon/utils/dungeon/Section.java
new file mode 100644
index 0000000..0ca6bbd
--- /dev/null
+++ b/src/main/java/blade/addon/utils/dungeon/Section.java
@@ -0,0 +1,200 @@
+package blade.addon.utils.dungeon;
+
+import blade.addon.utils.Constants;
+import blade.addon.utils.Debug;
+import blade.addon.utils.Location;
+import blade.addon.utils.Misc;
+import blade.addon.utils.events.Events;
+import config.practical.hud.HUDComponent;
+import config.practical.manager.ConfigValue;
+import net.minecraft.client.MinecraftClient;
+import net.minecraft.client.font.TextRenderer;
+import net.minecraft.client.gui.DrawContext;
+import net.minecraft.text.Text;
+import net.minecraft.util.Formatting;
+
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+public class Section {
+    public enum DisplayTerminalSplitsWhen {
+        BOSS("Entire Boss"), TERMINALS_ONLY("Terminals");
+
+        private final String label;
+
+        DisplayTerminalSplitsWhen(String label) {
+            this.label = label;
+        }
+
+        @Override
+        public String toString() {
+            return label;
+        }
+    }
+
+    private static final Split[] splits = {
+            new Split("1st", "", "", 0xffffffff),
+            new Split("2nd", "", "", 0xffffffff),
+            new Split("3rd", "", "", 0xffffffff),
+            new Split("4th", "", "", 0xffffffff),};
+
+    private static final Pattern TERMINALS_DONE_PATTERN = Pattern.compile("(activated|completed) (a terminal|a device|a lever)! \\((\\d)/(\\d)\\)$");
+
+    private static int currentSection = 0;
+    private static int completed = 0;
+    private static int total = 7;
+    private static boolean gateBlownUp = false;
+
+    @ConfigValue
+    public static boolean enableTerminalSplits = false;
+
+    @ConfigValue
+    public static boolean sendTermianlSplits = false;
+
+    @ConfigValue
+    public static DisplayTerminalSplitsWhen displayTerminalSplitsWhen = DisplayTerminalSplitsWhen.TERMINALS_ONLY;
+
+    public static void init() {
+        Events.ON_GAME_MESSAGE.register(Section::parseMessage);
+        Events.ON_LOCATION_CHANGE.register(newLocation -> {
+            if (Location.inDungeon()) {
+                reset();
+            }
+        });
+        Events.ON_PHASE_CHANGE.register(() -> {
+            if (Phase.inTerminals()) {
+                currentSection = 1;
+                splits[0].start();
+            } else if (Phase.inGoldorTunnel()) {
+                endAllSections();
+            }
+        });
+        Events.ON_SERVER_TICK.register(() -> {
+            if (currentSection < 1 || currentSection > 5) return;
+            for (Split split : splits) {
+                split.tick();
+            }
+        });
+    }
+
+    private static void reset() {
+        currentSection = 0;
+        resetSection();
+        for(Split split: splits) {
+            split.reset();
+        }
+    }
+
+    private static void resetSection() {
+        completed = 0;
+        gateBlownUp = false;
+    }
+
+    private static void incrementSection() {
+        resetSection();
+        endSplit(currentSection);
+        currentSection++;
+        startSplit(currentSection);
+    }
+
+    private static void endSplit(int section) {
+        int index = section - 1;
+        if (index < 0 || index >= splits.length) return;
+        splits[index].end();
+    }
+
+    private static void startSplit(int section) {
+        int index = section - 1;
+        if (index < 0 || index >= splits.length) return;
+        splits[index].start();
+    }
+
+    private static void endAllSections() {
+        for(Split split: splits) {
+            split.end();
+        }
+        if (sendTermianlSplits) {
+            Misc.addChatMessage(Text.literal("Splits: ").formatted(Formatting.GREEN));
+            for (Split split : splits) {
+                split.end();
+                Misc.addChatMessage(split.createNameText().append(split.createTimeText()));
+            }
+        }
+    }
+
+    private static void parseMessage(Text message) {
+        if (!Phase.inTerminals() || !enableTerminalSplits) return;
+
+        String string = message.getString();
+        Matcher matcher = TERMINALS_DONE_PATTERN.matcher(string);
+        if (matcher.find()) {
+            try {
+                int recentlyCompleted = Integer.parseInt(matcher.group(3));
+                if ((recentlyCompleted == total && gateBlownUp) || (recentlyCompleted < completed)) {
+                    incrementSection();
+                } else {
+                    total = Integer.parseInt(matcher.group(4));
+                    completed = recentlyCompleted;
+                }
+            } catch (NumberFormatException e) {
+                Debug.addDebugLog("Failed to parse terminal message, " + e.getMessage());
+            }
+
+
+        } else if (!gateBlownUp) {
+            if (string.equals("The gate has been destroyed!")) {
+                gateBlownUp = true;
+
+                if (completed == total) {
+                    incrementSection();
+                }
+
+            }
+        } else if (string.equals("The Core entrance is opening!")) {
+            //so in "goldor tunnel" can be shown after terms are done
+            currentSection = 5;
+            endAllSections();
+            Debug.sendDebugMessage(Text.literal("Core section"));
+        }
+    }
+
+
+    public static double getSection() {
+        return currentSection;
+    }
+
+    public static boolean isGateBlownUp() {
+        return gateBlownUp;
+    }
+
+    public static boolean inSection(int section) {
+        return currentSection == section && Phase.inP3();
+    }
+
+    public static boolean display() {
+        if (enableTerminalSplits && Location.inDungeon()) {
+
+            return switch (displayTerminalSplitsWhen) {
+                case BOSS -> Phase.inBoss();
+                case TERMINALS_ONLY -> Phase.inTerminals();
+            };
+
+        }
+
+        return false;
+    }
+
+    public static void render(HUDComponent component, DrawContext context) {
+        int x = component.getScaledX();
+        int y = component.getScaledY();
+
+        TextRenderer textRenderer = MinecraftClient.getInstance().textRenderer;
+
+        for (int i = 0; i < splits.length; i++) {
+            splits[i].drawSplit(context, textRenderer, x, y + Constants.TEXT_HEIGHT * i);
+        }
+    }
+
+    @ConfigValue
+    public static HUDComponent terminalSplits = new HUDComponent(0, 0, Split.SPLIT_LENGTH, 50, 1, "Term splits", Section::display, Section::render, () -> enableTerminalSplits);
+}
diff --git a/src/main/resources/blade-addons.mixins.json b/src/main/resources/blade-addons.mixins.json
index 0d9cfab..09d439b 100644
--- a/src/main/resources/blade-addons.mixins.json
+++ b/src/main/resources/blade-addons.mixins.json
@@ -5,6 +5,7 @@
   "mixins": [
     "ArrowMixin",
     "InventoryMixin",
+    "ItemStackMixin",
     "NetworkMixin",
     "ServerTickMixin"
   ],
@@ -17,6 +18,8 @@
   "client": [
     "BlockEntityRenderMixin",
     "FireMixin",
+    "HandledScreenMixin",
+    "InGameHudMixin",
     "NetworkMixin",
     "PositionMixin",
     "RenderMixin"
diff --git a/src/main/resources/fabric.mod.json b/src/main/resources/fabric.mod.json
index b0e8ff7..593d27f 100644
--- a/src/main/resources/fabric.mod.json
+++ b/src/main/resources/fabric.mod.json
@@ -3,9 +3,9 @@
 	"id": "blade-addons",
 	"version": "${version}",
 	"name": "blade-addons",
-	"description": "This is an example description! Tell everyone what your mod is about!",
+	"description": "Just some 1.21 mod that ports mods like valley",
 	"authors": [
-		"Me!"
+		"BladeMasterGabe"
 	],
 	"contact": {
 		"homepage": "https://fabricmc.net/",
- refactoring of where config values are storedGabriel Ellnebrand · 2025-12-06 · 3c4ddf2
查看变更
src/main/java/blade/addon/Bladeaddons.java                                    |   2 ++
 src/main/java/blade/addon/features/dungeon/AutoRequeue.java                   |   8 ++----
 src/main/java/blade/addon/features/dungeon/ChestCounter.java                  |  46 +++++++++++--------------------
 src/main/java/blade/addon/features/dungeon/DeathTickTimer.java                |  25 ++++++++---------
 src/main/java/blade/addon/features/dungeon/DupeClassChecker.java              |  26 ++++++++----------
 src/main/java/blade/addon/features/dungeon/ExplosiveShot.java                 |  11 +++-----
 src/main/java/blade/addon/features/dungeon/HidePlayers.java                   |  21 ++++----------
 src/main/java/blade/addon/features/dungeon/ItemHighlight.java                 |   8 ++----
 src/main/java/blade/addon/features/dungeon/KeyNotifier.java                   |  38 ++++++++++++--------------
 src/main/java/blade/addon/features/dungeon/LeapMessage.java                   |   7 ++---
 src/main/java/blade/addon/features/dungeon/SecretSpawnTimer.java              |  49 ++++++++++++++++-----------------
 src/main/java/blade/addon/features/dungeon/WarpCooldown.java                  |  52 ++++++++++++++++-------------------
 src/main/java/blade/addon/features/dungeon/f7/BossWaypoints.java              |  17 +++---------
 src/main/java/blade/addon/features/dungeon/f7/CrystalSpawn.java               |  29 ++++++++------------
 src/main/java/blade/addon/features/dungeon/f7/DistanceToLedge.java            |  28 ++++++++-----------
 src/main/java/blade/addon/features/dungeon/f7/GoldorTickTimer.java            |  50 +++++++++++++++-------------------
 src/main/java/blade/addon/features/dungeon/f7/InvincibilityTimer.java         | 100 +++++++++++++++++++++++++++++--------------------------------------
 src/main/java/blade/addon/features/dungeon/f7/PositionMessages.java           |   8 ++----
 src/main/java/blade/addon/features/dungeon/f7/RelicTimer.java                 |  49 ++++++++++++---------------------
 src/main/java/blade/addon/features/dungeon/f7/StormTickTimer.java             |  57 ++++++++++++++++----------------------
 src/main/java/blade/addon/features/dungeon/f7/TermStartTimer.java             |  32 ++++++++++------------
 src/main/java/blade/addon/mixin/ArrowMixin.java                               |   2 +-
 src/main/java/blade/addon/mixin/FireMixin.java                                |   2 +-
 src/main/java/blade/addon/mixin/NetworkMixin.java                             |   2 +-
 src/main/java/blade/addon/mixin/RenderMixin.java                              |   5 ++--
 src/main/java/blade/addon/utils/config/Config.java                            | 110 +++++++++++++++++++++++++++++++-------------------------------------------
 src/main/java/blade/addon/utils/config/components/Components.java             |  67 +++++++++++++++++++++++++++++++++++++++++++++
 src/main/java/blade/addon/utils/config/values/Dungeons.java                   |  73 +++++++++++++++++++++++++++++++++++++++++++++++++
 src/main/java/blade/addon/{features => utils/config/values}/ExtraOptions.java |   8 +-----
 src/main/java/blade/addon/utils/config/values/Floor7.java                     |  54 ++++++++++++++++++++++++++++++++++++
 30 files changed, 520 insertions(+), 466 deletions(-)
diff --git a/src/main/java/blade/addon/Bladeaddons.java b/src/main/java/blade/addon/Bladeaddons.java
index ab87a9f..395d057 100644
--- a/src/main/java/blade/addon/Bladeaddons.java
+++ b/src/main/java/blade/addon/Bladeaddons.java
@@ -26,6 +26,7 @@ import blade.addon.utils.Commands;
 import blade.addon.utils.Keybinds;
 import blade.addon.utils.Location;
 import blade.addon.utils.Scheduler;
+import blade.addon.utils.config.components.Components;
 import blade.addon.utils.config.Config;
 import blade.addon.utils.dungeon.DungeonClass;
 import blade.addon.utils.dungeon.Phase;
@@ -34,6 +35,7 @@ import net.fabricmc.api.ModInitializer;
 public class Bladeaddons implements ModInitializer {
 	@Override
 	public void onInitialize() {
+        Components.init();
         Config.manager.load();
 		Keybinds.register();
         Commands.register();
diff --git a/src/main/java/blade/addon/features/dungeon/AutoRequeue.java b/src/main/java/blade/addon/features/dungeon/AutoRequeue.java
index b24c991..47223ad 100644
--- a/src/main/java/blade/addon/features/dungeon/AutoRequeue.java
+++ b/src/main/java/blade/addon/features/dungeon/AutoRequeue.java
@@ -2,8 +2,8 @@ package blade.addon.features.dungeon;
 
 import blade.addon.utils.Location;
 import blade.addon.utils.Misc;
+import blade.addon.utils.config.values.Dungeons;
 import blade.addon.utils.events.Events;
-import config.practical.manager.ConfigValue;
 import net.fabricmc.fabric.api.client.message.v1.ClientReceiveMessageEvents;
 import net.minecraft.client.MinecraftClient;
 import net.minecraft.client.gui.hud.ChatHud;
@@ -24,14 +24,12 @@ public class AutoRequeue {
 
     private static final HashMap<String, Text> playerHashMap = new HashMap<>();
 
-    @ConfigValue
-    public static boolean enableAutoRequeue = false;
 
     private static boolean someoneLeft = false;
 
     public static void init() {
         ClientReceiveMessageEvents.GAME.register((message, overlay) -> {
-            if (!Location.inDungeon() && !enableAutoRequeue) return;
+            if (!Location.inDungeon() && !Dungeons.enableAutoRequeue) return;
             String string = message.getString();
             Matcher matcher = LEFT_PATTERN.matcher(string);
             if (matcher.find()) {
@@ -65,7 +63,7 @@ public class AutoRequeue {
         });
 
         Events.ON_RUN_END.register(() -> {
-            if (!enableAutoRequeue || someoneLeft) return;
+            if (!Dungeons.enableAutoRequeue || someoneLeft) return;
 
             if (playerHashMap.isEmpty()) {
                 Misc.executeCommand("instancerequeue");
diff --git a/src/main/java/blade/addon/features/dungeon/ChestCounter.java b/src/main/java/blade/addon/features/dungeon/ChestCounter.java
index 7beebf5..ab1beb3 100644
--- a/src/main/java/blade/addon/features/dungeon/ChestCounter.java
+++ b/src/main/java/blade/addon/features/dungeon/ChestCounter.java
@@ -1,11 +1,12 @@
 package blade.addon.features.dungeon;
 
 import blade.addon.utils.Location;
+import blade.addon.utils.config.values.Dungeons;
 import blade.addon.utils.dungeon.Phase;
 import blade.addon.utils.events.Events;
 import config.practical.hud.HUDComponent;
-import config.practical.manager.ConfigValue;
 import net.minecraft.client.MinecraftClient;
+import net.minecraft.client.gui.DrawContext;
 import net.minecraft.client.network.ClientPlayerEntity;
 import net.minecraft.sound.SoundEvents;
 import net.minecraft.text.Text;
@@ -17,16 +18,6 @@ public class ChestCounter {
 
     private static final Pattern PATTERN = Pattern.compile("^ Unclaimed chests: (\\d+)$");
 
-    @ConfigValue
-    public static boolean displayChestCount = false;
-    @ConfigValue
-    public static boolean onlyAfterRunOver = false;
-    @ConfigValue
-    public static boolean sendChestWarning = false;
-
-    @ConfigValue
-    public static int chestWarningCount = 55;
-
     private static int chestDisplayCount = 0;
     private static int countedChests = 0;
     private static boolean hasUpdatedData = false;
@@ -57,7 +48,7 @@ public class ChestCounter {
 
         Events.ON_RUN_END.register(() -> {
             countedChests++;
-            if (sendChestWarning && chestDisplayCount + countedChests >= chestWarningCount) {
+            if (Dungeons.sendChestWarning && chestDisplayCount + countedChests >= Dungeons.chestWarningCount) {
                 ClientPlayerEntity player = MinecraftClient.getInstance().player;
                 if (player != null) {
                     player.playSound(SoundEvents.BLOCK_NOTE_BLOCK_PLING.value(), 2, 1);
@@ -67,25 +58,20 @@ public class ChestCounter {
         });
     }
 
-    @ConfigValue
-    public static HUDComponent chestCounter = new HUDComponent(0, 0, 100, 10, 1, "Chest count",
-            () ->
-            {
-                if ((onlyAfterRunOver && !Phase.runOver())) return false;
-                return displayChestCount && Location.inDungeon();
-            },
-            ((hudComponent, drawContext) -> {
-                int x = hudComponent.getScaledX();
-                int y = hudComponent.getScaledY();
-
-                if (hasUpdatedData) {
-                    drawContext.drawText(MinecraftClient.getInstance().textRenderer, Text.literal("Chests: " + Math.min(chestDisplayCount + countedChests, 60)), x, y, 0xffffffff, true);
-                } else {
-                    drawContext.drawText(MinecraftClient.getInstance().textRenderer, Text.literal("go to the dungeon hub"), x, y, 0xffff0000, true);
+    public static boolean display() {
+        if ((Dungeons.onlyAfterRunOver && !Phase.runOver())) return false;
+        return Dungeons.displayChestCount && Location.inDungeon();
+    }
 
-                }
-            }), () -> displayChestCount
-    );
+    public static void render(HUDComponent component, DrawContext context) {
+        int x = component.getScaledX();
+        int y = component.getScaledY();
 
+        if (hasUpdatedData) {
+            context.drawText(MinecraftClient.getInstance().textRenderer, Text.literal("Chests: " + Math.min(chestDisplayCount + countedChests, 60)), x, y, 0xffffffff, true);
+        } else {
+            context.drawText(MinecraftClient.getInstance().textRenderer, Text.literal("go to the dungeon hub"), x, y, 0xffff0000, true);
 
+        }
+    }
 }
diff --git a/src/main/java/blade/addon/features/dungeon/DeathTickTimer.java b/src/main/java/blade/addon/features/dungeon/DeathTickTimer.java
index d39f6cb..e2e1adf 100644
--- a/src/main/java/blade/addon/features/dungeon/DeathTickTimer.java
+++ b/src/main/java/blade/addon/features/dungeon/DeathTickTimer.java
@@ -2,22 +2,20 @@ package blade.addon.features.dungeon;
 
 import blade.addon.utils.Constants;
 import blade.addon.utils.Location;
+import blade.addon.utils.config.values.Dungeons;
 import blade.addon.utils.events.Events;
 import blade.addon.utils.rendering.RenderUtils;
 import config.practical.hud.HUDComponent;
-import config.practical.manager.ConfigValue;
 import net.minecraft.client.MinecraftClient;
+import net.minecraft.client.gui.DrawContext;
 import net.minecraft.text.Text;
 import net.minecraft.util.math.Vec3d;
 
 public class DeathTickTimer {
 
-    private static final int WIDTH = 30;
     private static final int TOTAL_DEATH_TICKS = 40;
     private static Vec3d firstPosition;
 
-    @ConfigValue
-    public static boolean enableDeathTickTimer = false;
     private static int tick = TOTAL_DEATH_TICKS;
 
     public static void init() {
@@ -48,17 +46,16 @@ public class DeathTickTimer {
         }
     }
 
-    @ConfigValue
-    public static HUDComponent deathTickTimer = new HUDComponent(0, 0, WIDTH, 10, 1, "Death Tick Timer",
-            () -> enableDeathTickTimer && Location.inDungeon(),
-            ((hudComponent, drawContext) -> {
-                int x = hudComponent.getScaledX();
-                int y = hudComponent.getScaledY();
+    public static boolean display() {
+        return Dungeons.enableDeathTickTimer && Location.inDungeon();
+    }
 
-                double num = tick * Constants.TICK_DURATION;
+    public static void render(HUDComponent component, DrawContext context) {
+        int x = component.getScaledX();
+        int y = component.getScaledY();
 
-                RenderUtils.drawCenteredText(drawContext, MinecraftClient.getInstance().textRenderer, Text.literal(Constants.DECIMAL_FORMAT.format(num)), x, y, WIDTH);
+        double num = tick * Constants.TICK_DURATION;
+        RenderUtils.drawCenteredText(context, MinecraftClient.getInstance().textRenderer, Text.literal(Constants.DECIMAL_FORMAT.format(num)), x, y, component.getWidth());
 
-            }), () -> enableDeathTickTimer
-    );
+    }
 }
diff --git a/src/main/java/blade/addon/features/dungeon/DupeClassChecker.java b/src/main/java/blade/addon/features/dungeon/DupeClassChecker.java
index f79d615..74fecf7 100644
--- a/src/main/java/blade/addon/features/dungeon/DupeClassChecker.java
+++ b/src/main/java/blade/addon/features/dungeon/DupeClassChecker.java
@@ -1,12 +1,13 @@
 package blade.addon.features.dungeon;
 
 import blade.addon.utils.Location;
+import blade.addon.utils.config.values.Dungeons;
 import blade.addon.utils.dungeon.Phase;
 import blade.addon.utils.events.Events;
 import config.practical.hud.HUDComponent;
-import config.practical.manager.ConfigValue;
 import net.fabricmc.fabric.api.client.message.v1.ClientReceiveMessageEvents;
 import net.minecraft.client.MinecraftClient;
+import net.minecraft.client.gui.DrawContext;
 import net.minecraft.client.network.ClientPlayerEntity;
 import net.minecraft.client.world.ClientWorld;
 import net.minecraft.scoreboard.Scoreboard;
@@ -28,12 +29,9 @@ public class DupeClassChecker {
 
     private static boolean hasDuplicateClass = false;
 
-    @ConfigValue
-    public static boolean detectDuplicateClass = false;
-
     public static void init() {
         ClientReceiveMessageEvents.GAME.register((message, overlay) -> {
-            if (!Location.inDungeon() || Phase.runStarted() || !detectDuplicateClass) return;
+            if (!Location.inDungeon() || Phase.runStarted() || !Dungeons.detectDuplicateClass) return;
 
             Matcher matcher = STARTING_PATTERN.matcher(message.getString());
             if (!matcher.find()) return;
@@ -74,14 +72,14 @@ public class DupeClassChecker {
         return false;
     }
 
-    @ConfigValue
-    public static HUDComponent duplicateClassDisplay = new HUDComponent(0, 0, 130, 10, 1, "",
-            () -> Location.inDungeon() && hasDuplicateClass && !Phase.runStarted(),
-            ((hudComponent, drawContext) -> {
-                int x = hudComponent.getScaledX();
-                int y = hudComponent.getScaledY();
+    public static boolean display() {
+        return  Location.inDungeon() && hasDuplicateClass && !Phase.runStarted();
+    }
+
+    public static void render(HUDComponent component, DrawContext context) {
+        int x = component.getScaledX();
+        int y = component.getScaledY();
 
-                drawContext.drawText(MinecraftClient.getInstance().textRenderer, DISPLAY_TEXT, x, y, 0xffffffff, true);
-            }), () -> detectDuplicateClass
-    );
+        context.drawText(MinecraftClient.getInstance().textRenderer, DISPLAY_TEXT, x, y, 0xffffffff, true);
+    }
 }
diff --git a/src/main/java/blade/addon/features/dungeon/ExplosiveShot.java b/src/main/java/blade/addon/features/dungeon/ExplosiveShot.java
index ec244c0..3fdcbd4 100644
--- a/src/main/java/blade/addon/features/dungeon/ExplosiveShot.java
+++ b/src/main/java/blade/addon/features/dungeon/ExplosiveShot.java
@@ -2,8 +2,8 @@ package blade.addon.features.dungeon;
 
 import blade.addon.utils.Location;
 import blade.addon.utils.Misc;
+import blade.addon.utils.config.values.Dungeons;
 import blade.addon.utils.dungeon.Phase;
-import config.practical.manager.ConfigValue;
 import net.fabricmc.fabric.api.client.message.v1.ClientReceiveMessageEvents;
 import net.minecraft.text.Text;
 import net.minecraft.util.Formatting;
@@ -17,15 +17,12 @@ public class ExplosiveShot {
     private static final Pattern PATTERN = Pattern.compile("^Your Explosive Shot hit (\\d+) enemies for ([\\d,?.]+) damage\\.$");
     private static final DecimalFormat FORMAT = new DecimalFormat("###,###,###,###");
 
-    @ConfigValue
-    public static boolean calculateCriticalHit = false;
-    @ConfigValue
-    public static boolean onlyInBoss = false;
+
 
     public static void init() {
         ClientReceiveMessageEvents.GAME.register((message, overlay) -> {
-            if (!Location.inDungeon() || !calculateCriticalHit) return;
-            if (onlyInBoss && !Phase.inBoss()) return;
+            if (!Location.inDungeon() || !Dungeons.calculateCriticalHit) return;
+            if (Dungeons.onlyInBoss && !Phase.inBoss()) return;
 
             String string = message.getString();
 
diff --git a/src/main/java/blade/addon/features/dungeon/HidePlayers.java b/src/main/java/blade/addon/features/dungeon/HidePlayers.java
index e0aedd6..425bfd9 100644
--- a/src/main/java/blade/addon/features/dungeon/HidePlayers.java
+++ b/src/main/java/blade/addon/features/dungeon/HidePlayers.java
@@ -1,9 +1,9 @@
 package blade.addon.features.dungeon;
 
 import blade.addon.utils.Location;
+import blade.addon.utils.config.values.Dungeons;
 import blade.addon.utils.dungeon.Phase;
 import blade.addon.utils.events.Events;
-import config.practical.manager.ConfigValue;
 import net.minecraft.entity.player.PlayerEntity;
 import net.minecraft.util.math.Vec3d;
 
@@ -17,17 +17,6 @@ public class HidePlayers {
     private static long startTime;
     private static boolean justLeapt = false;
 
-    @ConfigValue
-    public static boolean hideAfterLeap = false;
-
-    @ConfigValue
-    public static boolean hideOnlyInBoss = true;
-
-    @ConfigValue
-    public static boolean hideAtSS = false;
-
-    @ConfigValue
-    public static boolean hideBeforeTermsOnly = false;
 
     public static void init() {
         Events.ON_LEAP.register(message -> {
@@ -37,8 +26,8 @@ public class HidePlayers {
     }
 
     public static boolean testHideAtLeap() {
-        if (!hideAfterLeap || !justLeapt) return false;
-        if (!Phase.inBoss() && hideOnlyInBoss) return false;
+        if (!Dungeons.hideAfterLeap || !justLeapt) return false;
+        if (!Phase.inBoss() && Dungeons.hideOnlyInBoss) return false;
 
         if (System.currentTimeMillis() - startTime >= HIDE_DURATION) {
             justLeapt = false;
@@ -49,8 +38,8 @@ public class HidePlayers {
     }
 
     public static boolean testHideAtSS(PlayerEntity player) {
-        if (!hideAtSS || !Phase.inBoss()) return false;
-        if (hideBeforeTermsOnly && Phase.inP3()) return false;
+        if (!Dungeons.hideAtSS || !Phase.inBoss()) return false;
+        if (Dungeons.hideBeforeTermsOnly && Phase.inP3()) return false;
 
         return SS_POSITION.distanceTo(player.getPos()) <= DISTANCE;
     }
diff --git a/src/main/java/blade/addon/features/dungeon/ItemHighlight.java b/src/main/java/blade/addon/features/dungeon/ItemHighlight.java
index bd3fcaf..6b115bd 100644
--- a/src/main/java/blade/addon/features/dungeon/ItemHighlight.java
+++ b/src/main/java/blade/addon/features/dungeon/ItemHighlight.java
@@ -2,10 +2,10 @@ package blade.addon.features.dungeon;
 
 import blade.addon.utils.Constants;
 import blade.addon.utils.Location;
+import blade.addon.utils.config.values.Dungeons;
 import blade.addon.utils.dungeon.Phase;
 import blade.addon.utils.rendering.RenderLayers;
 import blade.addon.utils.rendering.RenderUtils;
-import config.practical.manager.ConfigValue;
 import net.fabricmc.fabric.api.client.event.lifecycle.v1.ClientEntityEvents;
 import net.fabricmc.fabric.api.client.event.lifecycle.v1.ClientTickEvents;
 import net.fabricmc.fabric.api.client.rendering.v1.WorldRenderEvents;
@@ -34,8 +34,6 @@ public class ItemHighlight {
 
     private static final ConcurrentHashMap<ItemEntity, Integer> trackedItems = new ConcurrentHashMap<>();
 
-    @ConfigValue
-    public static boolean highlightItems = false;
 
     public static void init() {
         ClientTickEvents.END_CLIENT_TICK.register(minecraftClient -> {
@@ -61,7 +59,7 @@ public class ItemHighlight {
         });
 
         WorldRenderEvents.BEFORE_DEBUG_RENDER.register(worldRenderContext -> {
-            if (!highlightItems) return;
+            if (!Dungeons.highlightItems) return;
             Camera camera = worldRenderContext.camera();
             Vec3d cameraPos = camera.getPos();
             MatrixStack matrixStack = worldRenderContext.matrixStack();
@@ -90,7 +88,7 @@ public class ItemHighlight {
     }
 
     public static boolean highlightItem(ItemEntity item) {
-        if (!highlightItems || !Location.inDungeon() || Phase.inBoss()) return false;
+        if (!Dungeons.highlightItems || !Location.inDungeon() || Phase.inBoss()) return false;
 
         Text itemText = item.getStack().getName();
         if (itemText == null) return false;
diff --git a/src/main/java/blade/addon/features/dungeon/KeyNotifier.java b/src/main/java/blade/addon/features/dungeon/KeyNotifier.java
index dd197ce..7d60d79 100644
--- a/src/main/java/blade/addon/features/dungeon/KeyNotifier.java
+++ b/src/main/java/blade/addon/features/dungeon/KeyNotifier.java
@@ -1,13 +1,14 @@
 package blade.addon.features.dungeon;
 
 import blade.addon.utils.Location;
+import blade.addon.utils.config.values.Dungeons;
 import blade.addon.utils.dungeon.DungeonClass;
 import blade.addon.utils.events.Events;
 import config.practical.hud.HUDComponent;
-import config.practical.manager.ConfigValue;
 import net.fabricmc.fabric.api.client.event.lifecycle.v1.ClientTickEvents;
 import net.fabricmc.fabric.api.client.message.v1.ClientReceiveMessageEvents;
 import net.minecraft.client.MinecraftClient;
+import net.minecraft.client.gui.DrawContext;
 import net.minecraft.client.network.ClientPlayerEntity;
 import net.minecraft.sound.SoundEvents;
 import net.minecraft.text.Style;
@@ -30,12 +31,9 @@ public class KeyNotifier {
     private static boolean isBloodKey = false;
     private static boolean sentSound = false;
 
-    @ConfigValue
-    public static boolean enableKeyNotifier = false;
-
     public static void init() {
         Events.ON_ENTITY_TRACKED.register((entity, world) -> {
-            if (!Location.inDungeon() || hasKey || !enableKeyNotifier) return;
+            if (!Location.inDungeon() || hasKey || !Dungeons.enableKeyNotifier) return;
             if (!DungeonClass.isClass(DungeonClass.ARCHER) && !DungeonClass.isClass(DungeonClass.MAGE)) return;
 
 
@@ -55,7 +53,7 @@ public class KeyNotifier {
         });
 
         ClientReceiveMessageEvents.GAME.register((message, overlay) -> {
-            if (!Location.inDungeon() || !hasKey || !enableKeyNotifier) return;
+            if (!Location.inDungeon() || !hasKey || !Dungeons.enableKeyNotifier) return;
             if (!DungeonClass.isClass(DungeonClass.ARCHER) && !DungeonClass.isClass(DungeonClass.MAGE)) return;
 
             Matcher matcher = AUTOMATIC_PICKUP_PATTERN.matcher(message.getString());
@@ -93,19 +91,19 @@ public class KeyNotifier {
 
     }
 
-    @ConfigValue
-    public static HUDComponent keyNotifierDisplay = new HUDComponent(0, 0, 120, 10, 1, "",
-            () -> Location.inDungeon() && hasKey,
-            ((hudComponent, drawContext) -> {
-                int x = hudComponent.getScaledX();
-                int y = hudComponent.getScaledY();
-
-                if (isBloodKey) {
-                    drawContext.drawText(MinecraftClient.getInstance().textRenderer, BLOOD_KEY, x, y, 0xffffffff, true);
-                } else {
-                    drawContext.drawText(MinecraftClient.getInstance().textRenderer, WITHER_KEY, x, y, 0xffffffff, true);
-                }
+    public static boolean display() {
+        return Location.inDungeon() && hasKey;
+    }
 
-            }), () -> enableKeyNotifier
-    );
+    public static void render(HUDComponent component, DrawContext context) {
+        int x = component.getScaledX();
+        int y = component.getScaledY();
+
+        if (isBloodKey) {
+            context.drawText(MinecraftClient.getInstance().textRenderer, BLOOD_KEY, x, y, 0xffffffff, true);
+        } else {
+            context.drawText(MinecraftClient.getInstance().textRenderer, WITHER_KEY, x, y, 0xffffffff, true);
+        }
+
+    }
 }
diff --git a/src/main/java/blade/addon/features/dungeon/LeapMessage.java b/src/main/java/blade/addon/features/dungeon/LeapMessage.java
index 95db446..6e0c92f 100644
--- a/src/main/java/blade/addon/features/dungeon/LeapMessage.java
+++ b/src/main/java/blade/addon/features/dungeon/LeapMessage.java
@@ -1,8 +1,8 @@
 package blade.addon.features.dungeon;
 
 import blade.addon.utils.Location;
+import blade.addon.utils.config.values.Dungeons;
 import blade.addon.utils.events.Events;
-import config.practical.manager.ConfigValue;
 import net.fabricmc.fabric.api.client.message.v1.ClientReceiveMessageEvents;
 import net.minecraft.client.MinecraftClient;
 import net.minecraft.client.network.ClientPlayNetworkHandler;
@@ -15,9 +15,6 @@ public class LeapMessage {
     private static final int INDEX_REMOVAL = 9;
     private static final Pattern SEARCH_PATTERN = Pattern.compile("^You have teleported to .*!$");
 
-    @ConfigValue
-    public static boolean enableLeapMessages = false;
-
     public static void init() {
         ClientReceiveMessageEvents.GAME.register((message, overlay) -> {
             if (Location.inDungeon()) {
@@ -33,7 +30,7 @@ public class LeapMessage {
         });
 
         Events.ON_LEAP.register(message -> {
-            if (!enableLeapMessages) return;
+            if (!Dungeons.enableLeapMessages) return;
 
             ClientPlayNetworkHandler networkHandler = MinecraftClient.getInstance().getNetworkHandler();
             if (networkHandler == null) return;
diff --git a/src/main/java/blade/addon/features/dungeon/SecretSpawnTimer.java b/src/main/java/blade/addon/features/dungeon/SecretSpawnTimer.java
index 0acf8a8..819ab83 100644
--- a/src/main/java/blade/addon/features/dungeon/SecretSpawnTimer.java
+++ b/src/main/java/blade/addon/features/dungeon/SecretSpawnTimer.java
@@ -2,12 +2,13 @@ package blade.addon.features.dungeon;
 
 import blade.addon.utils.Constants;
 import blade.addon.utils.Location;
+import blade.addon.utils.config.values.Dungeons;
 import blade.addon.utils.dungeon.Phase;
 import blade.addon.utils.events.Events;
 import blade.addon.utils.rendering.RenderUtils;
 import config.practical.hud.HUDComponent;
-import config.practical.manager.ConfigValue;
 import net.minecraft.client.MinecraftClient;
+import net.minecraft.client.gui.DrawContext;
 import net.minecraft.text.Text;
 
 import java.util.regex.Matcher;
@@ -17,17 +18,13 @@ public class SecretSpawnTimer {
 
     private static final Pattern PATTERN = Pattern.compile("^Time Elapsed: ");
     private static final int TICKS_PER_SECOND = 20;
-    private static final int WIDTH = 20;
-
-    @ConfigValue
-    public static boolean enableSecretSpawnTimer = false;
 
     private static int tick = 0;
 
     public static void init() {
 
         Events.ON_TEAM.register(text -> {
-            if (!Location.inDungeon() || !enableSecretSpawnTimer) return;
+            if (!Location.inDungeon() || !Dungeons.enableSecretSpawnTimer) return;
 
             Matcher matcher = PATTERN.matcher(text);
             if (matcher.find()) {
@@ -38,24 +35,24 @@ public class SecretSpawnTimer {
         Events.ON_SERVER_TICK.register(() -> tick = Math.max(tick - 1, 0));
     }
 
-    @ConfigValue
-    public static HUDComponent secretSpawnTimer = new HUDComponent(0, 0, WIDTH, 10, 1, "Secret spawn timer",
-            () -> Location.inDungeon() && enableSecretSpawnTimer && !Phase.inBoss() && Phase.runStarted(),
-            ((hudComponent, drawContext) -> {
-                int x = hudComponent.getScaledX();
-                int y = hudComponent.getScaledY();
-
-                int color;
-                if (tick > 10) {
-                    color = Constants.GREEN_COLOR;
-                } else if (tick > 5) {
-                    color = Constants.ORANGE_COLOR;
-                } else {
-                    color = Constants.RED_COLOR;
-                }
-
-                RenderUtils.drawCenteredText(drawContext, MinecraftClient.getInstance().textRenderer, Text.literal(tick + ""), x, y, WIDTH, color);
-
-            }), () -> enableSecretSpawnTimer
-    );
+    public static boolean display() {
+        return  Location.inDungeon() && Dungeons.enableSecretSpawnTimer && !Phase.inBoss() && Phase.runStarted();
+    }
+
+    public static void render(HUDComponent component, DrawContext context) {
+        int x = component.getScaledX();
+        int y = component.getScaledY();
+
+        int color;
+        if (tick > 10) {
+            color = Constants.GREEN_COLOR;
+        } else if (tick > 5) {
+            color = Constants.ORANGE_COLOR;
+        } else {
+            color = Constants.RED_COLOR;
+        }
+
+        RenderUtils.drawCenteredText(context, MinecraftClient.getInstance().textRenderer, Text.literal(tick + ""), x, y, component.getWidth(), color);
+
+    }
 }
diff --git a/src/main/java/blade/addon/features/dungeon/WarpCooldown.java b/src/main/java/blade/addon/features/dungeon/WarpCooldown.java
index 44f36c4..b5fbb70 100644
--- a/src/main/java/blade/addon/features/dungeon/WarpCooldown.java
+++ b/src/main/java/blade/addon/features/dungeon/WarpCooldown.java
@@ -1,10 +1,11 @@
 package blade.addon.features.dungeon;
 
 import blade.addon.utils.Constants;
+import blade.addon.utils.config.values.Dungeons;
 import config.practical.hud.HUDComponent;
-import config.practical.manager.ConfigValue;
 import net.fabricmc.fabric.api.client.message.v1.ClientReceiveMessageEvents;
 import net.minecraft.client.MinecraftClient;
+import net.minecraft.client.gui.DrawContext;
 import net.minecraft.text.Text;
 import net.minecraft.util.Formatting;
 
@@ -19,12 +20,9 @@ public class WarpCooldown {
     private static long endTime = 0;
     private static boolean displayCooldown = false;
 
-    @ConfigValue
-    public static boolean enableWarpCooldown = false;
-
     public static void init() {
         ClientReceiveMessageEvents.GAME.register((message, overlay) -> {
-            if (!enableWarpCooldown) return;
+            if (!Dungeons.enableWarpCooldown) return;
 
             Matcher matcher = PATTERN.matcher(message.getString());
             if (matcher.find()) {
@@ -34,27 +32,25 @@ public class WarpCooldown {
         });
     }
 
-    @ConfigValue
-    public static HUDComponent warpCoolDown = new HUDComponent(0, 0, 110, 10, 0.75f, "Warp cooldown",
-            () -> {
-                if (!enableWarpCooldown || !displayCooldown) return false;
-
-                if ((endTime - System.currentTimeMillis()) > 0) {
-                    return true;
-                } else {
-                    displayCooldown = false;
-                    return false;
-                }
-            },
-            ((hudComponent, drawContext) -> {
-                int x = hudComponent.getScaledX();
-                int y = hudComponent.getScaledY();
-
-                double timeRemaining = Math.max((endTime - System.currentTimeMillis()) / 1000.0, 0);
-
-                Text text = Text.literal("Warp Cooldown: ").formatted(Formatting.DARK_PURPLE).append(Text.literal(Constants.DECIMAL_FORMAT.format(timeRemaining)).formatted(Formatting.WHITE));
-
-                drawContext.drawText(MinecraftClient.getInstance().textRenderer, text, x, y, 0xffffffff, true);
-            }), () ->enableWarpCooldown
-    );
+    public static boolean display()  {
+        if (!Dungeons.enableWarpCooldown || !displayCooldown) return false;
+
+        if ((endTime - System.currentTimeMillis()) > 0) {
+            return true;
+        } else {
+            displayCooldown = false;
+            return false;
+        }
+    }
+
+    public static void render(HUDComponent component, DrawContext context) {
+        int x = component.getScaledX();
+        int y = component.getScaledY();
+
+        double timeRemaining = Math.max((endTime - System.currentTimeMillis()) / 1000.0, 0);
+
+        Text text = Text.literal("Warp Cooldown: ").formatted(Formatting.DARK_PURPLE).append(Text.literal(Constants.DECIMAL_FORMAT.format(timeRemaining)).formatted(Formatting.WHITE));
+
+        context.drawText(MinecraftClient.getInstance().textRenderer, text, x, y, 0xffffffff, true);
+    }
 }
diff --git a/src/main/java/blade/addon/features/dungeon/f7/BossWaypoints.java b/src/main/java/blade/addon/features/dungeon/f7/BossWaypoints.java
index 6de1450..2dd8eeb 100644
--- a/src/main/java/blade/addon/features/dungeon/f7/BossWaypoints.java
+++ b/src/main/java/blade/addon/features/dungeon/f7/BossWaypoints.java
@@ -4,6 +4,7 @@ import blade.addon.utils.Constants;
 import blade.addon.utils.Location;
 import blade.addon.utils.Misc;
 import blade.addon.utils.Waypoint;
+import blade.addon.utils.config.values.Floor7;
 import blade.addon.utils.dungeon.Phase;
 import blade.addon.utils.rendering.RenderLayers;
 import blade.addon.utils.rendering.RenderUtils;
@@ -11,7 +12,6 @@ import com.google.gson.Gson;
 import com.google.gson.JsonArray;
 import com.google.gson.JsonElement;
 import com.google.gson.JsonObject;
-import config.practical.manager.ConfigValue;
 import net.fabricmc.fabric.api.client.rendering.v1.WorldRenderContext;
 import net.fabricmc.fabric.api.client.rendering.v1.WorldRenderEvents;
 import net.fabricmc.fabric.api.event.player.UseBlockCallback;
@@ -45,15 +45,6 @@ public class BossWaypoints {
 
     public static final CopyOnWriteArrayList<Waypoint> waypoints = new CopyOnWriteArrayList<>();
 
-    @ConfigValue
-    public static boolean enableBossWaypoints = false;
-
-    @ConfigValue
-    public static int nextWaypointColor = 0xff00F7F7;
-
-    @ConfigValue
-    public static boolean nextWaypointThroughWall = false;
-
     private static boolean ignoreBoss = false;
     private static boolean place = false;
 
@@ -66,7 +57,7 @@ public class BossWaypoints {
     }
 
     public static boolean isInValidArea() {
-        if (!enableBossWaypoints) return false;
+        if (!Floor7.enableBossWaypoints) return false;
         if (ignoreBoss) return true;
 
         return Location.inDungeon() && Phase.inBoss();
@@ -100,8 +91,8 @@ public class BossWaypoints {
     }
 
     private static void addWaypoint(double x, double y, double z, double dx, double dy, double dz) {
-        float[] colorAsFloat = RenderUtils.toFloats(nextWaypointColor);
-        waypoints.add(new Waypoint(x, y, z, dx, dy, dz, colorAsFloat[0], colorAsFloat[1], colorAsFloat[2], colorAsFloat[3], nextWaypointThroughWall));
+        float[] colorAsFloat = RenderUtils.toFloats(Floor7.nextWaypointColor);
+        waypoints.add(new Waypoint(x, y, z, dx, dy, dz, colorAsFloat[0], colorAsFloat[1], colorAsFloat[2], colorAsFloat[3],Floor7.nextWaypointThroughWall));
         save();
     }
 
diff --git a/src/main/java/blade/addon/features/dungeon/f7/CrystalSpawn.java b/src/main/java/blade/addon/features/dungeon/f7/CrystalSpawn.java
index c53e117..4451eae 100644
--- a/src/main/java/blade/addon/features/dungeon/f7/CrystalSpawn.java
+++ b/src/main/java/blade/addon/features/dungeon/f7/CrystalSpawn.java
@@ -2,13 +2,14 @@ package blade.addon.features.dungeon.f7;
 
 import blade.addon.utils.Constants;
 import blade.addon.utils.Location;
+import blade.addon.utils.config.values.Floor7;
 import blade.addon.utils.dungeon.Phase;
 import blade.addon.utils.events.Events;
 import blade.addon.utils.rendering.RenderUtils;
 import config.practical.hud.HUDComponent;
-import config.practical.manager.ConfigValue;
 import net.fabricmc.fabric.api.client.message.v1.ClientReceiveMessageEvents;
 import net.minecraft.client.MinecraftClient;
+import net.minecraft.client.gui.DrawContext;
 import net.minecraft.text.Text;
 import net.minecraft.util.Formatting;
 
@@ -20,18 +21,13 @@ public class CrystalSpawn {
     private static final Pattern PATTERN_1 = Pattern.compile("^\\[BOSS] Maxor: THAT BEAM! IT HURTS! IT HURTS!!$");
     private static final Pattern PATTERN_2 = Pattern.compile("^\\[BOSS] Maxor: YOU TRICKED ME!$");
 
-    @ConfigValue
-    public static boolean enableCrystalSpawnTime = false;
-
-    private static final int WIDTH = 30;
-
     private static final int TICK_SPAWN = 34;
     private static int tick = 0;
 
     public static void init() {
 
         ClientReceiveMessageEvents.GAME.register((message, overlay) -> {
-            if (!Location.inDungeon() || !Phase.inP1() || !enableCrystalSpawnTime) return;
+            if (!Location.inDungeon() || !Phase.inP1() || !Floor7.enableCrystalSpawnTime) return;
 
             Matcher matcher = PATTERN_1.matcher(message.getString());
             if (!matcher.find()) {
@@ -54,17 +50,16 @@ public class CrystalSpawn {
 
     }
 
-    @ConfigValue
-    public static HUDComponent crystalSpawnTime = new HUDComponent(0, 0, WIDTH, 10, 1, "Crystal Spawn Time",
-            () -> tick > 0 && Location.inDungeon(),
-            ((hudComponent, drawContext) -> {
-                int x = hudComponent.getScaledX();
-                int y = hudComponent.getScaledY();
+    public static boolean display() {
+        return  tick > 0 && Location.inDungeon() && Floor7.enableCrystalSpawnTime;
+    }
 
-                double num = tick * Constants.TICK_DURATION;
+    public static void render(HUDComponent component, DrawContext context) {
+        int x = component.getScaledX();
+        int y = component.getScaledY();
 
-                RenderUtils.drawCenteredText(drawContext, MinecraftClient.getInstance().textRenderer, Text.literal(Constants.DECIMAL_FORMAT.format(num)).formatted(Formatting.LIGHT_PURPLE), x, y, WIDTH);
+        double num = tick * Constants.TICK_DURATION;
 
-            }), () -> enableCrystalSpawnTime
-    );
+        RenderUtils.drawCenteredText(context, MinecraftClient.getInstance().textRenderer, Text.literal(Constants.DECIMAL_FORMAT.format(num)).formatted(Formatting.LIGHT_PURPLE), x, y, component.getWidth());
+    }
 }
diff --git a/src/main/java/blade/addon/features/dungeon/f7/DistanceToLedge.java b/src/main/java/blade/addon/features/dungeon/f7/DistanceToLedge.java
index ad41757..ff68309 100644
--- a/src/main/java/blade/addon/features/dungeon/f7/DistanceToLedge.java
+++ b/src/main/java/blade/addon/features/dungeon/f7/DistanceToLedge.java
@@ -2,22 +2,19 @@ package blade.addon.features.dungeon.f7;
 
 import blade.addon.utils.Constants;
 import blade.addon.utils.Location;
+import blade.addon.utils.config.values.Floor7;
 import blade.addon.utils.dungeon.DungeonClass;
 import blade.addon.utils.dungeon.Phase;
 import blade.addon.utils.rendering.RenderUtils;
 import config.practical.hud.HUDComponent;
-import config.practical.manager.ConfigValue;
 import net.minecraft.client.MinecraftClient;
+import net.minecraft.client.gui.DrawContext;
 import net.minecraft.client.network.ClientPlayerEntity;
 import net.minecraft.text.Text;
 
 public class DistanceToLedge {
 
     private static final double MIN_X = 33.704;
-    private static final int WIDTH = 30;
-
-    @ConfigValue
-    public static boolean displayDistanceToLedge = false;
 
     public static void init() {
 
@@ -30,18 +27,15 @@ public class DistanceToLedge {
         return player.getX() - MIN_X;
     }
 
-    @ConfigValue
-    public static HUDComponent distanceToLedgeComponent = new HUDComponent(0, 0, WIDTH, 10, 1, "Distance to ledge",
-            () -> {
-                if (!displayDistanceToLedge || !Location.inDungeon()) return false;
+    public static boolean display() {
+        if (!Floor7.displayDistanceToLedge || !Location.inDungeon()) return false;
+        return Phase.inP2() && !Phase.stormDead() && (DungeonClass.isClass(DungeonClass.MAGE) || DungeonClass.isClass(DungeonClass.ARCHER));
+    }
 
-                return Phase.inP2() && !Phase.stormDead() && (DungeonClass.isClass(DungeonClass.MAGE) || DungeonClass.isClass(DungeonClass.ARCHER));
-            },
-            ((hudComponent, drawContext) -> {
-                int x = hudComponent.getScaledX();
-                int y = hudComponent.getScaledY();
+    public static void render(HUDComponent component, DrawContext context) {
+        int x = component.getScaledX();
+        int y = component.getScaledY();
 
-                RenderUtils.drawCenteredText(drawContext, MinecraftClient.getInstance().textRenderer, Text.literal(Constants.DECIMAL_FORMAT.format(getDistance())), x, y, WIDTH);
-            }), () -> displayDistanceToLedge
-    );
+        RenderUtils.drawCenteredText(context, MinecraftClient.getInstance().textRenderer, Text.literal(Constants.DECIMAL_FORMAT.format(getDistance())), x, y, component.getWidth());
+    }
 }
diff --git a/src/main/java/blade/addon/features/dungeon/f7/GoldorTickTimer.java b/src/main/java/blade/addon/features/dungeon/f7/GoldorTickTimer.java
index aa3655a..c419fe5 100644
--- a/src/main/java/blade/addon/features/dungeon/f7/GoldorTickTimer.java
+++ b/src/main/java/blade/addon/features/dungeon/f7/GoldorTickTimer.java
@@ -2,23 +2,17 @@ package blade.addon.features.dungeon.f7;
 
 import blade.addon.utils.Constants;
 import blade.addon.utils.Location;
+import blade.addon.utils.config.values.Floor7;
 import blade.addon.utils.dungeon.Phase;
 import blade.addon.utils.events.Events;
 import blade.addon.utils.rendering.RenderUtils;
 import config.practical.hud.HUDComponent;
-import config.practical.manager.ConfigValue;
 import net.minecraft.client.MinecraftClient;
+import net.minecraft.client.gui.DrawContext;
 import net.minecraft.text.Text;
 
 public class GoldorTickTimer {
 
-    private static final int WIDTH = 30;
-
-    @ConfigValue
-    public static boolean enableGoldorTickTimer = false;
-
-    @ConfigValue
-    public static boolean inDeathTicks = true;
     private static int tick = 0;
 
     public static void init() {
@@ -33,29 +27,29 @@ public class GoldorTickTimer {
         });
     }
 
-    @ConfigValue
-    public static HUDComponent goldorTickTimer = new HUDComponent(0, 0, WIDTH, 10, 1, "Goldor Tick Timer",
-            () -> enableGoldorTickTimer && Location.inDungeon() && Phase.inTerminals(),
-            ((hudComponent, drawContext) -> {
-                int x = hudComponent.getScaledX();
-                int y = hudComponent.getScaledY();
+    public static boolean display() {
+        return  Floor7.enableGoldorTickTimer && Location.inDungeon() && Phase.inTerminals();
+    }
 
-                int color;
-                double num = tick * Constants.TICK_DURATION;
+    public static void render(HUDComponent component, DrawContext context) {
+        int x = component.getScaledX();
+        int y = component.getScaledY();
 
-                double mod = num % 3;
-                if (mod < 1) {
-                    color = Constants.GREEN_COLOR;
-                } else if (mod < 2) {
-                    color = Constants.ORANGE_COLOR;
-                } else {
-                    color = Constants.RED_COLOR;
-                }
+        int color;
+        double num = tick * Constants.TICK_DURATION;
 
-                if (inDeathTicks) num = mod;
+        double mod = num % 3;
+        if (mod < 1) {
+            color = Constants.GREEN_COLOR;
+        } else if (mod < 2) {
+            color = Constants.ORANGE_COLOR;
+        } else {
+            color = Constants.RED_COLOR;
+        }
 
-                RenderUtils.drawCenteredText(drawContext, MinecraftClient.getInstance().textRenderer, Text.literal(Constants.DECIMAL_FORMAT.format(num)), x, y, WIDTH, color);
+        if (Floor7.inDeathTicks) num = mod;
 
-            }), () -> enableGoldorTickTimer
-    );
+        RenderUtils.drawCenteredText(context, MinecraftClient.getInstance().textRenderer, Text.literal(Constants.DECIMAL_FORMAT.format(num)), x, y, component.getWidth(), color);
+
+    }
 }
diff --git a/src/main/java/blade/addon/features/dungeon/f7/InvincibilityTimer.java b/src/main/java/blade/addon/features/dungeon/f7/InvincibilityTimer.java
index 394f806..517ac73 100644
--- a/src/main/java/blade/addon/features/dungeon/f7/InvincibilityTimer.java
+++ b/src/main/java/blade/addon/features/dungeon/f7/InvincibilityTimer.java
@@ -3,10 +3,10 @@ package blade.addon.features.dungeon.f7;
 import blade.addon.utils.Constants;
 import blade.addon.utils.Location;
 import blade.addon.utils.Misc;
+import blade.addon.utils.config.values.Dungeons;
 import blade.addon.utils.dungeon.Phase;
 import blade.addon.utils.events.Events;
 import config.practical.hud.HUDComponent;
-import config.practical.manager.ConfigValue;
 import net.fabricmc.fabric.api.client.message.v1.ClientReceiveMessageEvents;
 import net.minecraft.client.MinecraftClient;
 import net.minecraft.client.gl.RenderPipelines;
@@ -70,18 +70,6 @@ public class InvincibilityTimer {
         }
     }
 
-    @ConfigValue
-    public static boolean displayInvincibilityTimer = false;
-
-    @ConfigValue
-    public static boolean showProcTitle = false;
-
-    @ConfigValue
-    public static DisplayWhen displayWhen = DisplayWhen.ALWAYS;
-
-    @ConfigValue
-    public static boolean useSprites = false;
-
     public static void init() {
         Events.ON_SERVER_TICK.register(InvincibilityTimer::tick);
         ClientReceiveMessageEvents.GAME.register(InvincibilityTimer::detectPet);
@@ -143,19 +131,19 @@ public class InvincibilityTimer {
         Matcher matcher = BONZO_PATTERN.matcher(string);
         if (matcher.matches()) {
             bonzoMaskTicks = BONZO_MASK_COOLDOWN;
-            if (showProcTitle) {
+            if (Dungeons.showProcTitle) {
                 Misc.setTitle(Text.literal("Bonzo"));
             }
         }
         if (string.equals("Second Wind Activated! Your Spirit Mask saved your life!")) {
             spiritMaskTicks = SPIRIT_MASK_COOLDOWN;
-            if (showProcTitle) {
+            if (Dungeons.showProcTitle) {
                 Misc.setTitle(Text.literal("Spirit"));
             }
         }
         if (string.equals("Your Phoenix Pet saved you from certain death!")) {
             phoenixTicks = PHOENIX_COOLDOWN;
-            if (showProcTitle) {
+            if (Dungeons.showProcTitle) {
                 Misc.setTitle(Text.literal("Phoenix"));
             }
         }
@@ -223,45 +211,43 @@ public class InvincibilityTimer {
         return spiritMaskTicks > 0;
     }
 
-    @ConfigValue
-    public static HUDComponent invincibilityTimer = new HUDComponent(0, 0, 110, 28, 1, "Invincibility timer",
-            () -> {
-                if (!displayInvincibilityTimer || !Location.inDungeon()) {
-                    return false;
-                }
-
-                switch (displayWhen) {
-                    case BOSS_ONLY -> {
-                        return Phase.inBoss();
-                    }
-                    case P3_ONLY -> {
-                        return Phase.inP3();
-                    }
-                    case ALWAYS -> {
-                        return true;
-                    }
-                    case USEFUL_PHASES -> {
-                        return Phase.inP3() || Phase.inP2();
-                    }
-                    case null, default -> {
-                        return false;
-                    }
-                }
-            },
-            ((hudComponent, drawContext) -> {
-                int x = hudComponent.getScaledX();
-                int y = hudComponent.getScaledY();
-
-                if (useSprites) {
-                    drawSprite(drawContext, BONZO_SPRITE, x, y, bonzoMaskOn, bonzoMaskTicks, formatTimer(bonzoMaskTicks));
-                    drawSprite(drawContext, SPIRIT_SPRITE, x, y + SPRITE_SIZE + 1, spiritMaskOn, spiritMaskTicks, formatTimer(spiritMaskTicks));
-                    drawSprite(drawContext, PHOENIX_SPRITE, x, y + (SPRITE_SIZE + 1) * 2, phoenixOn, phoenixTicks, formatTimer(phoenixTicks));
-
-                } else {
-                    drawContext.drawText(MinecraftClient.getInstance().textRenderer, getBonzoText(), x, y, 0xffffffff, true);
-                    drawContext.drawText(MinecraftClient.getInstance().textRenderer, getSpiritText(), x, y + TEXT_HEIGHT, 0xffffffff, true);
-                    drawContext.drawText(MinecraftClient.getInstance().textRenderer, getPhoenixText(), x, y + TEXT_HEIGHT * 2, 0xffffffff, true);
-                }
-            }), () -> displayInvincibilityTimer
-    );
+    public static boolean display() {
+        if (!Dungeons.displayInvincibilityTimer || !Location.inDungeon()) {
+            return false;
+        }
+
+        switch (Dungeons.displayWhen) {
+            case BOSS_ONLY -> {
+                return Phase.inBoss();
+            }
+            case P3_ONLY -> {
+                return Phase.inP3();
+            }
+            case ALWAYS -> {
+                return true;
+            }
+            case USEFUL_PHASES -> {
+                return Phase.inP3() || Phase.inP2();
+            }
+            case null, default -> {
+                return false;
+            }
+        }
+    }
+
+    public static void render(HUDComponent component, DrawContext context) {
+        int x = component.getScaledX();
+        int y = component.getScaledY();
+
+        if (Dungeons.useSprites) {
+            drawSprite(context, BONZO_SPRITE, x, y, bonzoMaskOn, bonzoMaskTicks, formatTimer(bonzoMaskTicks));
+            drawSprite(context, SPIRIT_SPRITE, x, y + SPRITE_SIZE + 1, spiritMaskOn, spiritMaskTicks, formatTimer(spiritMaskTicks));
+            drawSprite(context, PHOENIX_SPRITE, x, y + (SPRITE_SIZE + 1) * 2, phoenixOn, phoenixTicks, formatTimer(phoenixTicks));
+
+        } else {
+            context.drawText(MinecraftClient.getInstance().textRenderer, getBonzoText(), x, y, 0xffffffff, true);
+            context.drawText(MinecraftClient.getInstance().textRenderer, getSpiritText(), x, y + TEXT_HEIGHT, 0xffffffff, true);
+            context.drawText(MinecraftClient.getInstance().textRenderer, getPhoenixText(), x, y + TEXT_HEIGHT * 2, 0xffffffff, true);
+        }
+    }
 }
diff --git a/src/main/java/blade/addon/features/dungeon/f7/PositionMessages.java b/src/main/java/blade/addon/features/dungeon/f7/PositionMessages.java
index 1cd2684..f336854 100644
--- a/src/main/java/blade/addon/features/dungeon/f7/PositionMessages.java
+++ b/src/main/java/blade/addon/features/dungeon/f7/PositionMessages.java
@@ -2,10 +2,10 @@ package blade.addon.features.dungeon.f7;
 
 import blade.addon.utils.JsonUtility;
 import blade.addon.utils.Location;
+import blade.addon.utils.config.values.Floor7;
 import blade.addon.utils.dungeon.Phase;
 import blade.addon.utils.dungeon.PositionMessage;
 import blade.addon.utils.events.Events;
-import config.practical.manager.ConfigValue;
 import net.fabricmc.fabric.api.client.event.lifecycle.v1.ClientTickEvents;
 import net.minecraft.client.MinecraftClient;
 import net.minecraft.client.network.ClientPlayerEntity;
@@ -14,9 +14,6 @@ import java.util.ArrayList;
 
 public class PositionMessages {
 
-    @ConfigValue
-    public static boolean enablePositionalMessages = false;
-
     private static final ArrayList<PositionMessage> positionMessages = JsonUtility.readPositionalMessages("/data/positions.json");
 
     public static void init() {
@@ -32,8 +29,7 @@ public class PositionMessages {
     }
 
     public static void tick(MinecraftClient client) {
-        if (!Location.inDungeon()) return;
-        if (!Phase.inP3()) return;
+        if (!Location.inDungeon() || !Phase.inP3() || !Floor7.enablePositionalMessages) return;
         ClientPlayerEntity player = client.player;
         if (player == null) return;
 
diff --git a/src/main/java/blade/addon/features/dungeon/f7/RelicTimer.java b/src/main/java/blade/addon/features/dungeon/f7/RelicTimer.java
index 72f7f06..228316c 100644
--- a/src/main/java/blade/addon/features/dungeon/f7/RelicTimer.java
+++ b/src/main/java/blade/addon/features/dungeon/f7/RelicTimer.java
@@ -3,12 +3,12 @@ package blade.addon.features.dungeon.f7;
 import blade.addon.utils.Constants;
 import blade.addon.utils.Location;
 import blade.addon.utils.Misc;
+import blade.addon.utils.config.values.Floor7;
 import blade.addon.utils.dungeon.Phase;
 import blade.addon.utils.events.Events;
 import blade.addon.utils.rendering.RenderLayers;
 import blade.addon.utils.rendering.RenderUtils;
 import config.practical.hud.HUDComponent;
-import config.practical.manager.ConfigValue;
 import net.fabricmc.fabric.api.client.message.v1.ClientReceiveMessageEvents;
 import net.fabricmc.fabric.api.client.rendering.v1.WorldRenderEvents;
 import net.fabricmc.fabric.api.event.player.AttackBlockCallback;
@@ -16,6 +16,7 @@ import net.fabricmc.fabric.api.event.player.UseBlockCallback;
 import net.minecraft.block.BlockState;
 import net.minecraft.block.Blocks;
 import net.minecraft.client.MinecraftClient;
+import net.minecraft.client.gui.DrawContext;
 import net.minecraft.client.network.ClientPlayerEntity;
 import net.minecraft.client.render.Camera;
 import net.minecraft.client.render.VertexConsumer;
@@ -53,27 +54,12 @@ public class RelicTimer {
     private static final int GREEN_COLOR = 0xff00ff00;
     private static final int RED_COLOR = 0xffff0000;
 
-    private static final int WIDTH = 30;
-
     private static final Pattern PATTERN = Pattern.compile("^(.+) picked the Corrupted (Red|Purple|Orange|Green|Blue) Relic!$");
 
     private static long pickupTime;
     private static Relic pickedupRelic = null;
 
-    @ConfigValue
-    public static boolean enableRelicStartTimer = false;
-
-    @ConfigValue
-    public static int relicSpawnTicks = 42;
-
-    @ConfigValue
-    public static boolean enableRelicPlaceTime = false;
-
-    @ConfigValue
-    public static boolean renderRelicHighlight = false;
-
-
-    private static int tick = relicSpawnTicks;
+    private static int tick = Floor7.relicSpawnTicks;
 
     public static void init() {
         Events.ON_SERVER_TICK.register(() -> {
@@ -83,7 +69,7 @@ public class RelicTimer {
 
         Events.ON_LOCATION_CHANGE.register(newLocation -> {
             if (Location.inDungeon()) {
-                tick = relicSpawnTicks;
+                tick = Floor7.relicSpawnTicks;
                 pickedupRelic = null;
             }
         });
@@ -95,7 +81,7 @@ public class RelicTimer {
         });
 
         ClientReceiveMessageEvents.GAME.register((message, overlay) -> {
-            if (!Location.inDungeon() || !Phase.inP5() || !enableRelicPlaceTime) return;
+            if (!Location.inDungeon() || !Phase.inP5() || !Floor7.enableRelicPlaceTime) return;
 
             String str = message.getString().replaceAll("§.", "");
             Matcher matcher = PATTERN.matcher(str);
@@ -128,7 +114,7 @@ public class RelicTimer {
         });
 
         WorldRenderEvents.BEFORE_DEBUG_RENDER.register(worldRenderContext -> {
-            if (pickedupRelic == null || !renderRelicHighlight) return;
+            if (pickedupRelic == null || !Floor7.renderRelicHighlight) return;
 
             Camera camera = worldRenderContext.camera();
             Vec3d cameraPos = camera.getPos();
@@ -157,7 +143,7 @@ public class RelicTimer {
         if (block.getBlock() != Blocks.ANVIL && block.getBlock() != Blocks.CAULDRON) return;
         double distance = pickedupRelic.box.getCenter().distanceTo(pos.toCenterPos());
         if (distance <= 1) {
-            if (enableRelicPlaceTime) {
+            if (Floor7.enableRelicPlaceTime) {
             double diff = (System.currentTimeMillis() - pickupTime) / 1000.0;
                 Misc.addChatMessage(Text.literal("The ").formatted(Formatting.GREEN)
                         .append(Text.literal(pickedupRelic.name().toLowerCase()).withColor(pickedupRelic.color))
@@ -170,20 +156,19 @@ public class RelicTimer {
 
     }
 
-    @ConfigValue
-    public static HUDComponent relicSpawnTimer = new HUDComponent(0, 0, WIDTH, 10, 1, "Relic Spawn Timer",
-            () -> enableRelicStartTimer && Location.inDungeon() && Phase.inP5() && tick > 0,
-            ((hudComponent, drawContext) -> {
-                int x = hudComponent.getScaledX();
-                int y = hudComponent.getScaledY();
+    public static boolean display() {
+        return  Floor7.enableRelicStartTimer && Location.inDungeon() && Phase.inP5() && tick > 0;
+    }
 
-                int color = tick > 7 ? GREEN_COLOR : RED_COLOR;
+    public static void render(HUDComponent component, DrawContext context)  {
+        int x = component.getScaledX();
+        int y = component.getScaledY();
 
-                double num = tick * Constants.TICK_DURATION;
+        int color = tick > 7 ? GREEN_COLOR : RED_COLOR;
 
-                RenderUtils.drawCenteredText(drawContext, MinecraftClient.getInstance().textRenderer, Text.literal(Constants.DECIMAL_FORMAT.format(num)), x, y, WIDTH, color);
+        double num = tick * Constants.TICK_DURATION;
 
-            }), () -> enableRelicStartTimer
-    );
+        RenderUtils.drawCenteredText(context, MinecraftClient.getInstance().textRenderer, Text.literal(Constants.DECIMAL_FORMAT.format(num)), x, y, component.getWidth(), color);
 
+    }
 }
diff --git a/src/main/java/blade/addon/features/dungeon/f7/StormTickTimer.java b/src/main/java/blade/addon/features/dungeon/f7/StormTickTimer.java
index e9a794a..60e0460 100644
--- a/src/main/java/blade/addon/features/dungeon/f7/StormTickTimer.java
+++ b/src/main/java/blade/addon/features/dungeon/f7/StormTickTimer.java
@@ -3,14 +3,15 @@ package blade.addon.features.dungeon.f7;
 import blade.addon.utils.Constants;
 import blade.addon.utils.Location;
 import blade.addon.utils.Misc;
+import blade.addon.utils.config.values.Floor7;
 import blade.addon.utils.dungeon.DungeonClass;
 import blade.addon.utils.dungeon.Phase;
 import blade.addon.utils.events.Events;
 import blade.addon.utils.rendering.RenderUtils;
 import config.practical.hud.HUDComponent;
-import config.practical.manager.ConfigValue;
 import net.fabricmc.fabric.api.client.message.v1.ClientReceiveMessageEvents;
 import net.minecraft.client.MinecraftClient;
+import net.minecraft.client.gui.DrawContext;
 import net.minecraft.text.Text;
 import net.minecraft.util.Formatting;
 
@@ -22,18 +23,8 @@ public class StormTickTimer {
     private static final Pattern PATTERN = Pattern.compile("^⚠ Storm is enraged! ⚠$");
 
     private static final long DEATH_DISPLAY_DURATION = 2000;
-    private static final int WIDTH = 30;
     private static final int WARN_TICK = 20 * 20;
 
-    @ConfigValue
-    public static boolean enableStormTickTimer = false;
-
-    @ConfigValue
-    public static boolean enableStormDeathTime = false;
-
-    @ConfigValue
-    public static boolean notifyUsedSpiritMask = false;
-
     private static int tick = 0;
     private static double deathTime = 0;
     private static long deathStartDisplayTime = 0;
@@ -42,7 +33,7 @@ public class StormTickTimer {
         Events.ON_SERVER_TICK.register(() -> {
             if (Location.inDungeon() && Phase.inP2() && !Phase.stormDead()) tick++;
 
-            if (tick == WARN_TICK && notifyUsedSpiritMask && DungeonClass.isClass(DungeonClass.MAGE) && InvincibilityTimer.spiritMaskUsed()) {
+            if (tick == WARN_TICK && Floor7.notifyUsedSpiritMask && DungeonClass.isClass(DungeonClass.MAGE) && InvincibilityTimer.spiritMaskUsed()) {
                 Misc.setTitle(Text.literal("Leap to arch"));
             }
         });
@@ -56,7 +47,7 @@ public class StormTickTimer {
         });
 
         ClientReceiveMessageEvents.GAME.register((message, overlay) -> {
-            if (!Location.inDungeon() || !Phase.inP2() || !enableStormDeathTime) return;
+            if (!Location.inDungeon() || !Phase.inP2() || !Floor7.enableStormDeathTime) return;
 
             Matcher matcher = PATTERN.matcher(message.getString());
 
@@ -72,30 +63,30 @@ public class StormTickTimer {
 
         });
     }
-    
-    @ConfigValue
-    public static HUDComponent stormTickTimer = new HUDComponent(0, 0, WIDTH, 10, 1, "Storm Tick Timer",
-            () -> enableStormTickTimer && Location.inDungeon() && Phase.inP2() && !Phase.stormDead(),
-            ((hudComponent, drawContext) -> {
-                int x = hudComponent.getScaledX();
-                int y = hudComponent.getScaledY();
 
-                double num = tick * Constants.TICK_DURATION;
+    public static boolean display() {
+        return Floor7.enableStormTickTimer && Location.inDungeon() && Phase.inP2() && !Phase.stormDead();
+    }
+
+    public static void render(HUDComponent component, DrawContext context) {
+        int x = component.getScaledX();
+        int y = component.getScaledY();
 
-                RenderUtils.drawCenteredText(drawContext, MinecraftClient.getInstance().textRenderer, Constants.DECIMAL_FORMAT.format(num), x, y, WIDTH);
+        double num = tick * Constants.TICK_DURATION;
 
-            })
-    );
+        RenderUtils.drawCenteredText(context, MinecraftClient.getInstance().textRenderer, Constants.DECIMAL_FORMAT.format(num), x, y, component.getWidth());
 
-    @ConfigValue
-    public static HUDComponent stormDeathTime = new HUDComponent(0, 0, WIDTH, 10, 1, "Storm Death Time",
-            () -> enableStormTickTimer && Location.inDungeon() && Phase.inP2() && !Phase.stormDead() && deathTime > 0 && deathStartDisplayTime > System.currentTimeMillis() - DEATH_DISPLAY_DURATION,
-            ((hudComponent, drawContext) -> {
-                int x = hudComponent.getScaledX();
-                int y = hudComponent.getScaledY();
+    }
+
+    public static boolean displayDeathTime() {
+        return Floor7.enableStormDeathTime && Location.inDungeon() && Phase.inP2() && !Phase.stormDead() && deathTime > 0 && deathStartDisplayTime > System.currentTimeMillis() - DEATH_DISPLAY_DURATION;
+    }
 
-                RenderUtils.drawCenteredText(drawContext, MinecraftClient.getInstance().textRenderer, Text.literal(Constants.DECIMAL_FORMAT.format(deathTime)).formatted(Formatting.DARK_PURPLE), x, y, WIDTH);
+    public static void renderDeathTime(HUDComponent component, DrawContext context) {
+        int x = component.getScaledX();
+        int y = component.getScaledY();
 
-            }), () -> enableStormTickTimer
-    );
+        RenderUtils.drawCenteredText(context, MinecraftClient.getInstance().textRenderer, Text.literal(Constants.DECIMAL_FORMAT.format(deathTime)).formatted(Formatting.DARK_PURPLE), x, y, component.getWidth());
+
+    }
 }
diff --git a/src/main/java/blade/addon/features/dungeon/f7/TermStartTimer.java b/src/main/java/blade/addon/features/dungeon/f7/TermStartTimer.java
index 48642f1..9756a62 100644
--- a/src/main/java/blade/addon/features/dungeon/f7/TermStartTimer.java
+++ b/src/main/java/blade/addon/features/dungeon/f7/TermStartTimer.java
@@ -2,21 +2,19 @@ package blade.addon.features.dungeon.f7;
 
 import blade.addon.utils.Constants;
 import blade.addon.utils.Location;
+import blade.addon.utils.config.values.Floor7;
 import blade.addon.utils.dungeon.Phase;
 import blade.addon.utils.events.Events;
 import blade.addon.utils.rendering.RenderUtils;
 import config.practical.hud.HUDComponent;
-import config.practical.manager.ConfigValue;
 import net.minecraft.client.MinecraftClient;
+import net.minecraft.client.gui.DrawContext;
 import net.minecraft.text.Text;
 
 public class TermStartTimer {
 
-    private static final int WIDTH = 30;
     private static final int TOTAL_TICKS = 100;
 
-    @ConfigValue
-    public static boolean enableTermStartTimer = false;
     private static int tick = 100;
 
     public static void init() {
@@ -30,17 +28,17 @@ public class TermStartTimer {
             }
         });
     }
-    
-    @ConfigValue
-    public static HUDComponent termStartTimer = new HUDComponent(0, 0, WIDTH, 10, 1, "Term Start Timer",
-            () -> enableTermStartTimer && Location.inDungeon() && Phase.inP2() && Phase.stormDead(),
-            ((hudComponent, drawContext) -> {
-                int x = hudComponent.getScaledX();
-                int y = hudComponent.getScaledY();
-
-                double num = tick * Constants.TICK_DURATION;
-                RenderUtils.drawCenteredText(drawContext, MinecraftClient.getInstance().textRenderer, Text.literal(Constants.DECIMAL_FORMAT.format(num)), x, y, WIDTH, 0xFFFFFF55);
-
-            }), () -> enableTermStartTimer
-    );
+
+    public static boolean display() {
+        return Floor7.enableTermStartTimer && Location.inDungeon() && Phase.inP2() && Phase.stormDead();
+    }
+
+    public static void render(HUDComponent component, DrawContext context) {
+        int x = component.getScaledX();
+        int y = component.getScaledY();
+
+        double num = tick * Constants.TICK_DURATION;
+        RenderUtils.drawCenteredText(context, MinecraftClient.getInstance().textRenderer, Text.literal(Constants.DECIMAL_FORMAT.format(num)), x, y, component.getWidth(), 0xFFFFFF55);
+
+    }
 }
diff --git a/src/main/java/blade/addon/mixin/ArrowMixin.java b/src/main/java/blade/addon/mixin/ArrowMixin.java
index 11da02c..58ce169 100644
--- a/src/main/java/blade/addon/mixin/ArrowMixin.java
+++ b/src/main/java/blade/addon/mixin/ArrowMixin.java
@@ -1,6 +1,6 @@
 package blade.addon.mixin;
 
-import blade.addon.features.ExtraOptions;
+import blade.addon.utils.config.values.ExtraOptions;
 import net.minecraft.client.render.entity.feature.StuckArrowsFeatureRenderer;
 import org.spongepowered.asm.mixin.Mixin;
 import org.spongepowered.asm.mixin.injection.At;
diff --git a/src/main/java/blade/addon/mixin/FireMixin.java b/src/main/java/blade/addon/mixin/FireMixin.java
index cbc6ca0..a1cabe1 100644
--- a/src/main/java/blade/addon/mixin/FireMixin.java
+++ b/src/main/java/blade/addon/mixin/FireMixin.java
@@ -1,7 +1,7 @@
 package blade.addon.mixin;
 
-import blade.addon.features.ExtraOptions;
 import blade.addon.utils.Misc;
+import blade.addon.utils.config.values.ExtraOptions;
 import net.minecraft.client.render.entity.EntityRenderer;
 import net.minecraft.client.render.entity.state.EntityRenderState;
 import net.minecraft.entity.Entity;
diff --git a/src/main/java/blade/addon/mixin/NetworkMixin.java b/src/main/java/blade/addon/mixin/NetworkMixin.java
index 9b6e3bb..6284c5d 100644
--- a/src/main/java/blade/addon/mixin/NetworkMixin.java
+++ b/src/main/java/blade/addo
…… diff 过长已截断 ……
- added /refill - added chestCounter only in boss - added calc exposhot only in boss - added a check for if your in skyblock to some things - added some diana notifier (will get moved)Gabriel Ellnebrand · 2025-12-04 · 4d9dd97
查看变更
gradle.properties                                                 |  2 +-
 src/main/java/blade/addon/Bladeaddons.java                        | 18 ++++++++++--------
 src/main/java/blade/addon/features/DianaNotifier.java             | 65 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 src/main/java/blade/addon/features/dungeon/AutoRequeue.java       |  9 +--------
 src/main/java/blade/addon/features/dungeon/ChestCounter.java      | 10 ++++++++--
 src/main/java/blade/addon/features/dungeon/ExplosiveShot.java     |  7 +++++--
 src/main/java/blade/addon/features/dungeon/f7/StormTickTimer.java |  2 +-
 src/main/java/blade/addon/utils/Commands.java                     | 14 ++++++++++++++
 src/main/java/blade/addon/utils/Location.java                     | 34 ++++++++++++++++++----------------
 src/main/java/blade/addon/utils/Scheduler.java                    | 60 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 src/main/java/blade/addon/utils/config/Config.java                | 21 ++++++++++++++++++---
 src/main/java/blade/addon/utils/dungeon/Phase.java                |  4 ++++
 12 files changed, 205 insertions(+), 41 deletions(-)
diff --git a/gradle.properties b/gradle.properties
index 174cbc1..32b236c 100644
--- a/gradle.properties
+++ b/gradle.properties
@@ -13,7 +13,7 @@ loader_version=0.17.3
 loom_version=1.13-SNAPSHOT
 
 # Mod Properties
-mod_version=2.2.0
+mod_version=2.2.2
 maven_group=blade.addon
 archives_base_name=blade-addons
 
diff --git a/src/main/java/blade/addon/Bladeaddons.java b/src/main/java/blade/addon/Bladeaddons.java
index 1d5d9b4..ab87a9f 100644
--- a/src/main/java/blade/addon/Bladeaddons.java
+++ b/src/main/java/blade/addon/Bladeaddons.java
@@ -1,26 +1,27 @@
 package blade.addon;
 
+import blade.addon.features.DianaNotifier;
 import blade.addon.features.dungeon.AutoRequeue;
 import blade.addon.features.dungeon.ChestCounter;
-import blade.addon.features.dungeon.f7.BossWaypoints;
-import blade.addon.features.dungeon.f7.CrystalSpawn;
 import blade.addon.features.dungeon.DeathTickTimer;
 import blade.addon.features.dungeon.DupeClassChecker;
 import blade.addon.features.dungeon.ExplosiveShot;
-import blade.addon.features.dungeon.f7.DistanceToLedge;
-import blade.addon.features.dungeon.f7.GoldorTickTimer;
 import blade.addon.features.dungeon.HidePlayers;
-import blade.addon.features.dungeon.f7.InvincibilityTimer;
+import blade.addon.features.dungeon.ItemHighlight;
 import blade.addon.features.dungeon.KeyNotifier;
 import blade.addon.features.dungeon.LeapMessage;
 import blade.addon.features.dungeon.MobHighlight;
-import blade.addon.features.dungeon.ItemHighlight;
+import blade.addon.features.dungeon.SecretSpawnTimer;
+import blade.addon.features.dungeon.WarpCooldown;
+import blade.addon.features.dungeon.f7.BossWaypoints;
+import blade.addon.features.dungeon.f7.CrystalSpawn;
+import blade.addon.features.dungeon.f7.DistanceToLedge;
+import blade.addon.features.dungeon.f7.GoldorTickTimer;
+import blade.addon.features.dungeon.f7.InvincibilityTimer;
 import blade.addon.features.dungeon.f7.PositionMessages;
 import blade.addon.features.dungeon.f7.RelicTimer;
-import blade.addon.features.dungeon.SecretSpawnTimer;
 import blade.addon.features.dungeon.f7.StormTickTimer;
 import blade.addon.features.dungeon.f7.TermStartTimer;
-import blade.addon.features.dungeon.WarpCooldown;
 import blade.addon.utils.Commands;
 import blade.addon.utils.Keybinds;
 import blade.addon.utils.Location;
@@ -61,5 +62,6 @@ public class Bladeaddons implements ModInitializer {
         DistanceToLedge.init();
         ItemHighlight.init();
         BossWaypoints.init();
+        DianaNotifier.init();
     }
 }
\ No newline at end of file
diff --git a/src/main/java/blade/addon/features/DianaNotifier.java b/src/main/java/blade/addon/features/DianaNotifier.java
new file mode 100644
index 0000000..77f250d
--- /dev/null
+++ b/src/main/java/blade/addon/features/DianaNotifier.java
@@ -0,0 +1,65 @@
+package blade.addon.features;
+
+import blade.addon.utils.Misc;
+import blade.addon.utils.Scheduler;
+import blade.addon.utils.events.Events;
+import config.practical.manager.ConfigValue;
+import net.minecraft.client.MinecraftClient;
+import net.minecraft.client.network.ClientPlayerEntity;
+import net.minecraft.sound.SoundEvents;
+import net.minecraft.text.Text;
+
+public class DianaNotifier {
+
+    @ConfigValue
+    public static boolean sendSound = false;
+    @ConfigValue
+    public static boolean sendWaypoint = false;
+    @ConfigValue
+    public static boolean checkHarpy = false;
+    @ConfigValue
+    public static boolean checkBull = false;
+    @ConfigValue
+    public static boolean checkNymph = false;
+
+    public static void init() {
+        Events.ON_GAME_MESSAGE.register(message-> {
+            String string = message.getString();
+            String lowerCase = string.toLowerCase();
+            if (!lowerCase.contains("you dug out a")) return;
+            if (checkHarpy) {
+                check(lowerCase, "harpy");
+            }
+
+            if (checkBull) {
+                check(lowerCase, "cretan bull");
+
+            }
+
+            if (checkNymph) {
+                check(lowerCase, "stranded nymph");
+            }
+        });
+    }
+
+    private static void check(String string, String name) {
+        if (string.contains(name)) {
+            MinecraftClient client = MinecraftClient.getInstance();
+            Misc.setTitle(Text.literal(name));
+            ClientPlayerEntity player = client.player;
+            if (player == null) {
+                Misc.addChatMessage(Text.literal("Player is somehow null"));
+                return;
+            }
+
+            if (sendSound) {
+                Scheduler.scheduleSound(SoundEvents.BLOCK_NOTE_BLOCK_PLING.value(), 2, 1);
+
+            }
+
+            if (sendWaypoint) {
+                Scheduler.scheduleCommand("pc x: " + (int) player.getX() + ", y: " + (int) player.getY() + ", z: " + (int) player.getZ());
+            }
+        }
+    }
+}
diff --git a/src/main/java/blade/addon/features/dungeon/AutoRequeue.java b/src/main/java/blade/addon/features/dungeon/AutoRequeue.java
index effa54c..b24c991 100644
--- a/src/main/java/blade/addon/features/dungeon/AutoRequeue.java
+++ b/src/main/java/blade/addon/features/dungeon/AutoRequeue.java
@@ -21,8 +21,6 @@ public class AutoRequeue {
     private static final Pattern LEFT_PATTERN = Pattern.compile("has left the party.$");
 
     private static final int MESSAGE_OFFSET = 2;
-    private static final int NO_RANK_OFFSET = 12;
-
 
     private static final HashMap<String, Text> playerHashMap = new HashMap<>();
 
@@ -46,16 +44,11 @@ public class AutoRequeue {
             if (!matcher.find()) return;
 
             int startOfMessage = string.indexOf(":");
-            int startOfUsername = string.indexOf("]");
 
             if (startOfMessage == -1 || startOfMessage + MESSAGE_OFFSET >= string.length()) return;
 
-            if (startOfUsername == -1) {
-                startOfUsername = NO_RANK_OFFSET;
-            }
-
             String realMessage = string.substring(startOfMessage + MESSAGE_OFFSET);
-            String username = string.substring(startOfUsername + MESSAGE_OFFSET, startOfMessage - MESSAGE_OFFSET);
+            String username = string.substring(0, startOfMessage - MESSAGE_OFFSET);
 
             if (playerHashMap.containsKey(username)) {
                 matcher = UNDT_PATTERN.matcher(realMessage);
diff --git a/src/main/java/blade/addon/features/dungeon/ChestCounter.java b/src/main/java/blade/addon/features/dungeon/ChestCounter.java
index 1c6a07e..7beebf5 100644
--- a/src/main/java/blade/addon/features/dungeon/ChestCounter.java
+++ b/src/main/java/blade/addon/features/dungeon/ChestCounter.java
@@ -1,6 +1,7 @@
 package blade.addon.features.dungeon;
 
 import blade.addon.utils.Location;
+import blade.addon.utils.dungeon.Phase;
 import blade.addon.utils.events.Events;
 import config.practical.hud.HUDComponent;
 import config.practical.manager.ConfigValue;
@@ -18,7 +19,8 @@ public class ChestCounter {
 
     @ConfigValue
     public static boolean displayChestCount = false;
-
+    @ConfigValue
+    public static boolean onlyAfterRunOver = false;
     @ConfigValue
     public static boolean sendChestWarning = false;
 
@@ -67,7 +69,11 @@ public class ChestCounter {
 
     @ConfigValue
     public static HUDComponent chestCounter = new HUDComponent(0, 0, 100, 10, 1, "Chest count",
-            () -> displayChestCount && Location.inDungeon(),
+            () ->
+            {
+                if ((onlyAfterRunOver && !Phase.runOver())) return false;
+                return displayChestCount && Location.inDungeon();
+            },
             ((hudComponent, drawContext) -> {
                 int x = hudComponent.getScaledX();
                 int y = hudComponent.getScaledY();
diff --git a/src/main/java/blade/addon/features/dungeon/ExplosiveShot.java b/src/main/java/blade/addon/features/dungeon/ExplosiveShot.java
index 7820aaf..ec244c0 100644
--- a/src/main/java/blade/addon/features/dungeon/ExplosiveShot.java
+++ b/src/main/java/blade/addon/features/dungeon/ExplosiveShot.java
@@ -2,6 +2,7 @@ package blade.addon.features.dungeon;
 
 import blade.addon.utils.Location;
 import blade.addon.utils.Misc;
+import blade.addon.utils.dungeon.Phase;
 import config.practical.manager.ConfigValue;
 import net.fabricmc.fabric.api.client.message.v1.ClientReceiveMessageEvents;
 import net.minecraft.text.Text;
@@ -18,11 +19,13 @@ public class ExplosiveShot {
 
     @ConfigValue
     public static boolean calculateCriticalHit = false;
+    @ConfigValue
+    public static boolean onlyInBoss = false;
 
     public static void init() {
         ClientReceiveMessageEvents.GAME.register((message, overlay) -> {
-            if (!Location.inDungeon()) return;
-            if (!calculateCriticalHit) return;
+            if (!Location.inDungeon() || !calculateCriticalHit) return;
+            if (onlyInBoss && !Phase.inBoss()) return;
 
             String string = message.getString();
 
diff --git a/src/main/java/blade/addon/features/dungeon/f7/StormTickTimer.java b/src/main/java/blade/addon/features/dungeon/f7/StormTickTimer.java
index ba528c8..e9a794a 100644
--- a/src/main/java/blade/addon/features/dungeon/f7/StormTickTimer.java
+++ b/src/main/java/blade/addon/features/dungeon/f7/StormTickTimer.java
@@ -23,7 +23,7 @@ public class StormTickTimer {
 
     private static final long DEATH_DISPLAY_DURATION = 2000;
     private static final int WIDTH = 30;
-    private static final int WARN_TICK = 23 * 20;
+    private static final int WARN_TICK = 20 * 20;
 
     @ConfigValue
     public static boolean enableStormTickTimer = false;
diff --git a/src/main/java/blade/addon/utils/Commands.java b/src/main/java/blade/addon/utils/Commands.java
index 8c49f2f..b93c22d 100644
--- a/src/main/java/blade/addon/utils/Commands.java
+++ b/src/main/java/blade/addon/utils/Commands.java
@@ -35,6 +35,18 @@ public class Commands {
 
                             .then(ClientCommandManager.literal("ij").executes(context -> FillHelper.fillItem(MinecraftClient.getInstance(), FillHelper.INFLATABLE_JERRY, 64, 64)))
 
+                            .then(ClientCommandManager.literal("refill")
+                                    .executes(
+                                            context -> {
+                                                FillHelper.fillItem(MinecraftClient.getInstance(), FillHelper.ENDER_PEARL, 16, 16);
+                                                Scheduler.scheduleTask(() -> FillHelper.fillItem(MinecraftClient.getInstance(), FillHelper.SUPERBOOM_TNT, 64, 64), 40);
+                                                Scheduler.scheduleTask(() -> FillHelper.fillItem(MinecraftClient.getInstance(), FillHelper.INFLATABLE_JERRY, 64, 64), 80);
+                                                return Constants.SUCCESS;
+                                            }
+
+                                    ))
+
+
                             .then(ClientCommandManager.literal("waypoint")
 
                                     .then(ClientCommandManager.literal("add")
@@ -141,6 +153,8 @@ public class Commands {
                                     )
                             )
 
+
+
                             .executes(commandContext -> Scheduler.scheduleScreen(Config.createScreen(null)))
             );
 
diff --git a/src/main/java/blade/addon/utils/Location.java b/src/main/java/blade/addon/utils/Location.java
index 9470828..d371b13 100644
--- a/src/main/java/blade/addon/utils/Location.java
+++ b/src/main/java/blade/addon/utils/Location.java
@@ -1,36 +1,32 @@
 package blade.addon.utils;
 
 import blade.addon.utils.events.Events;
+import net.hypixel.data.type.ServerType;
 import net.hypixel.modapi.HypixelModAPI;
 import net.hypixel.modapi.packet.impl.clientbound.event.ClientboundLocationPacket;
 
 public enum Location {
     NONE,
     DUNGEON,
-    DUNGEON_HUB;
+    DUNGEON_HUB,
+    PRIVATE_ISLAND,
+    HUB;
 
     private static Location currentLocation = Location.NONE;
+    private static boolean inSkyblock = false;
 
     public static void init() {
 
-           /*
-            System.out.println(packet.getMap().toString());
-            System.out.println(packet.getLobbyName().toString());
-            System.out.println(packet.getMode().toString());
-            System.out.println(packet.getServerName());
-            System.out.println(packet.getServerType().toString());
-
-                [STDOUT]: Optional[Private Island]
-                [STDOUT]: Optional.empty
-                [STDOUT]: Optional[dynamic]
-                [STDOUT]: mini87DF
-                [STDOUT]: Optional[SKYBLOCK]
-            */
-
         HypixelModAPI instance = HypixelModAPI.getInstance();
+        instance.createHandler(ClientboundLocationPacket.class, packet -> packet.getMap().ifPresent(map -> {
+
+            if (packet.getServerType().isPresent()) {
+                ServerType serverType = packet.getServerType().get();
+                inSkyblock = serverType.getName().equals("SkyBlock");
+            }
 
+            System.out.println(map.toUpperCase().replace(" ", "_"));
 
-        instance.createHandler(ClientboundLocationPacket.class, packet -> packet.getMap().ifPresent(map -> {
             try {
                 currentLocation = Location.valueOf(map.toUpperCase().replace(" ", "_"));
             } catch (IllegalArgumentException ignored) {
@@ -48,10 +44,16 @@ public enum Location {
     }
 
     public static boolean in(Location location) {
+        if (!inSkyblock) return false;
         return currentLocation == location;
     }
 
     public static boolean inDungeon() {
+        if (!inSkyblock) return false;
         return currentLocation == Location.DUNGEON;
     }
+
+    public static boolean inSkyblock() {
+        return inSkyblock;
+    }
 }
\ No newline at end of file
diff --git a/src/main/java/blade/addon/utils/Scheduler.java b/src/main/java/blade/addon/utils/Scheduler.java
index dfcff6e..dba8924 100644
--- a/src/main/java/blade/addon/utils/Scheduler.java
+++ b/src/main/java/blade/addon/utils/Scheduler.java
@@ -3,11 +3,31 @@ package blade.addon.utils;
 import com.mojang.brigadier.Command;
 import net.fabricmc.fabric.api.client.event.lifecycle.v1.ClientTickEvents;
 import net.minecraft.client.gui.screen.Screen;
+import net.minecraft.client.network.ClientPlayerEntity;
+import net.minecraft.sound.SoundEvent;
+
+import java.util.concurrent.CopyOnWriteArrayList;
 
 public class Scheduler {
 
     private static Screen scheduledScreen = null;
     private static int screenTicks = 0;
+    private static SoundEvent scheduledSound = null;
+    private static float scheduledVolume = 0;
+    private static float scheduledPitch = 0;
+    private static String scheduledCommand = null;
+
+    static class Task {
+        Runnable task;
+        int delay;
+
+        public Task(Runnable task, int delay) {
+            this.task = task;
+            this.delay = delay;
+        }
+    }
+
+    private static final CopyOnWriteArrayList<Task> tasks = new CopyOnWriteArrayList<>();
 
     public static void init() {
         ClientTickEvents.START_CLIENT_TICK.register(minecraftClient -> {
@@ -18,6 +38,32 @@ public class Scheduler {
                     scheduledScreen = null;
                 }
             }
+
+            if (scheduledSound != null) {
+                ClientPlayerEntity player = minecraftClient.player;
+                if (player != null) {
+                    player.playSound(scheduledSound, scheduledVolume, scheduledPitch);
+                    scheduledSound = null;
+                }
+            }
+
+            if (scheduledCommand != null) {
+                ClientPlayerEntity player = minecraftClient.player;
+                if (player != null && player.networkHandler != null) {
+                    player.networkHandler.sendChatCommand(scheduledCommand);
+                    scheduledCommand = null;
+                }
+            }
+
+            for (int i = tasks.size() - 1; i >= 0; i--) {
+                Task task = tasks.get(i);
+                task.delay--;
+                if (task.delay <= 0) {
+                    minecraftClient.execute(task.task);
+                    tasks.remove(i);
+                }
+            }
+
         });
     }
 
@@ -27,4 +73,18 @@ public class Scheduler {
         screenTicks = 1;
         return Command.SINGLE_SUCCESS;
     }
+
+    public static void scheduleSound(SoundEvent soundEvent, float volume, float pitch) {
+        scheduledSound = soundEvent;
+        scheduledVolume = volume;
+        scheduledPitch = pitch;
+    }
+
+    public static void scheduleCommand(String command) {
+        scheduledCommand = command;
+    }
+
+    public static void scheduleTask(Runnable runnable, int ticks) {
+        tasks.add(new Task(runnable, ticks));
+    }
 }
diff --git a/src/main/java/blade/addon/utils/config/Config.java b/src/main/java/blade/addon/utils/config/Config.java
index 76d8944..1208933 100644
--- a/src/main/java/blade/addon/utils/config/Config.java
+++ b/src/main/java/blade/addon/utils/config/Config.java
@@ -1,5 +1,6 @@
 package blade.addon.utils.config;
 
+import blade.addon.features.DianaNotifier;
 import blade.addon.features.dungeon.AutoRequeue;
 import blade.addon.features.dungeon.ChestCounter;
 import blade.addon.features.dungeon.ItemHighlight;
@@ -46,7 +47,7 @@ public class Config {
             List.of(StormTickTimer.class, GoldorTickTimer.class, Phase.class, LeapMessage.class, PositionMessages.class, TermStartTimer.class, Split.class, DeathTickTimer.class, ExplosiveShot.class,
                     InvincibilityTimer.class, WarpCooldown.class, DupeClassChecker.class, HidePlayers.class, CrystalSpawn.class,
                     KeyNotifier.class, RelicTimer.class, AutoRequeue.class, MobHighlight.class, ChestCounter.class, SecretSpawnTimer.class, DistanceToLedge.class, ItemHighlight.class, ExtraOptions.class,
-                    BossWaypoints.class));
+                    BossWaypoints.class, DianaNotifier.class));
 
     public static Screen createScreen(Screen parent) {
         ConfigurableScreen screen = new ConfigurableScreen(TITLE, parent, manager);
@@ -75,15 +76,20 @@ public class Config {
 
         ConfigSection chests = new ConfigSection(Text.literal("Croesus"));
         chests.add(new ConfigBool(Text.literal("Display current chest count"), () -> ChestCounter.displayChestCount, bool -> ChestCounter.displayChestCount = bool));
+        chests.add(new ConfigBool(Text.literal("Only display after run is over"), () -> ChestCounter.onlyAfterRunOver, bool -> ChestCounter.onlyAfterRunOver = bool));
         chests.add(new ConfigBool(Text.literal("Send chest count warning"), () -> ChestCounter.sendChestWarning, bool -> ChestCounter.sendChestWarning = bool));
         chests.add(new ConfigInt(Text.literal("Warning at chest"), () -> ChestCounter.chestWarningCount, num -> ChestCounter.chestWarningCount = num, 1, 1, 60));
 
         dungeons.add(chests);
-
         dungeons.add(new ConfigBool(Text.literal("Leap message"), () -> LeapMessage.enableLeapMessages, bool -> LeapMessage.enableLeapMessages = bool));
         dungeons.add(new ConfigBool(Text.literal("Auto requeue"), () -> AutoRequeue.enableAutoRequeue, bool -> AutoRequeue.enableAutoRequeue = bool));
         dungeons.add(new ConfigBool(Text.literal("Death tick timer (not accurate currently)"), () -> DeathTickTimer.enableDeathTickTimer, bool -> DeathTickTimer.enableDeathTickTimer = bool));
-        dungeons.add(new ConfigBool(Text.literal("calculate explosive shot"), () -> ExplosiveShot.calculateCriticalHit, bool -> ExplosiveShot.calculateCriticalHit = bool));
+
+        ConfigSection expo = new ConfigSection(Text.literal("Explosive shot"));
+        expo.add(new ConfigBool(Text.literal("calculate explosive shot"), () -> ExplosiveShot.calculateCriticalHit, bool -> ExplosiveShot.calculateCriticalHit = bool));
+        expo.add(new ConfigBool(Text.literal("Only in boss"), () -> ExplosiveShot.onlyInBoss, bool -> ExplosiveShot.onlyInBoss = bool));
+        dungeons.add(expo);
+
         dungeons.add(new ConfigBool(Text.literal("Notification on key spawn"), () -> KeyNotifier.enableKeyNotifier, bool -> KeyNotifier.enableKeyNotifier = bool));
         dungeons.add(new ConfigBool(Text.literal("Secret spawn timer"), () -> SecretSpawnTimer.enableSecretSpawnTimer, bool -> SecretSpawnTimer.enableSecretSpawnTimer = bool));
         dungeons.add(new ConfigBool(Text.literal("Item highlight"), () -> ItemHighlight.highlightItems, bool -> ItemHighlight.highlightItems = bool));
@@ -173,6 +179,15 @@ public class Config {
         extra.add(new ConfigBool(Text.literal("Disable ability on cooldown sound"), () -> ExtraOptions.disableAbilityCooldownSound, bool -> ExtraOptions.disableAbilityCooldownSound = bool));
         extra.add(new ConfigBool(Text.literal("Hide dead entities"), () -> ExtraOptions.hideDeadEntities, bool -> ExtraOptions.hideDeadEntities = bool));
 
+        ConfigSection diana = new ConfigSection(Text.literal("Diana notifications"));
+        diana.add(new ConfigBool(Text.literal("Send Sound"), () -> DianaNotifier.sendSound, bool ->  DianaNotifier.sendSound = bool));
+        diana.add(new ConfigBool(Text.literal("Send Waypoint"), () ->  DianaNotifier.sendWaypoint, bool ->   DianaNotifier.sendWaypoint = bool));
+        diana.add(new ConfigBool(Text.literal("Check Harpy"), () -> DianaNotifier.checkHarpy, bool -> DianaNotifier.checkHarpy = bool));
+        diana.add(new ConfigBool(Text.literal("Check Bull"), () -> DianaNotifier.checkBull, bool -> DianaNotifier.checkBull = bool));
+        diana.add(new ConfigBool(Text.literal("Check Nymph"), () -> DianaNotifier.checkNymph, bool -> DianaNotifier.checkNymph = bool));
+
+        extra.add(diana);
+
         screen.addCategory(extra);
 
 
diff --git a/src/main/java/blade/addon/utils/dungeon/Phase.java b/src/main/java/blade/addon/utils/dungeon/Phase.java
index 6408424..ac42754 100644
--- a/src/main/java/blade/addon/utils/dungeon/Phase.java
+++ b/src/main/java/blade/addon/utils/dungeon/Phase.java
@@ -257,6 +257,10 @@ public class Phase {
         return currentPhase == 9 && inFloor7;
     }
 
+    public static boolean runOver() {
+        return runOver;
+    }
+
     @ConfigValue
     public static HUDComponent splitTimer = new HUDComponent(0, 0, Split.SPLIT_LENGTH, 100, 1, "Splits",
             () -> Location.inDungeon() && enableSplits && Phase.runStarted(),
- added boss waypoints - fixed reque working even if someone leaves the party - made the chestcounter update in the dungeon hub while opening chests - added a title on use for InvincibilityTimer - added a warning if spirit mask is used for py - added /dh command for dungeon hubGabriel Ellnebrand · 2025-11-30 · 45cdec8
查看变更
gradle.properties                                                     |   2 +-
 src/main/java/blade/addon/Bladeaddons.java                            |   2 +
 src/main/java/blade/addon/features/dungeon/AutoRequeue.java           |  27 +++++----
 src/main/java/blade/addon/features/dungeon/ChestCounter.java          |   7 +--
 src/main/java/blade/addon/features/dungeon/DupeClassChecker.java      |   7 +++
 src/main/java/blade/addon/features/dungeon/ExplosiveShot.java         |   6 +-
 src/main/java/blade/addon/features/dungeon/MobHighlight.java          |  62 +++++++++++++++-----
 src/main/java/blade/addon/features/dungeon/f7/BossWaypoints.java      | 263 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 src/main/java/blade/addon/features/dungeon/f7/InvincibilityTimer.java | 103 ++++++++++++++++++--------------
 src/main/java/blade/addon/features/dungeon/f7/RelicTimer.java         |   5 +-
 src/main/java/blade/addon/features/dungeon/f7/StormTickTimer.java     |  14 ++++-
 src/main/java/blade/addon/mixin/FireMixin.java                        |  10 ++--
 src/main/java/blade/addon/utils/Commands.java                         | 115 ++++++++++++++++++++++++++++++++++++
 src/main/java/blade/addon/utils/Constants.java                        |   3 +
 src/main/java/blade/addon/utils/Debug.java                            |  28 +++++++++
 src/main/java/blade/addon/utils/Misc.java                             |  33 +++++++++++
 src/main/java/blade/addon/utils/Waypoint.java                         |  60 +++++++++++++++++++
 src/main/java/blade/addon/utils/config/Config.java                    |  12 +++-
 src/main/java/blade/addon/utils/dungeon/FillHelper.java               |  12 ++--
 src/main/java/blade/addon/utils/dungeon/Phase.java                    | 188 ++++++++++++++++++++++++++++++++++------------------------
 src/main/java/blade/addon/utils/dungeon/Split.java                    |  68 +++++++++++----------
 src/main/java/blade/addon/utils/rendering/RenderPipelines.java        |  20 +++----
 22 files changed, 831 insertions(+), 216 deletions(-)
diff --git a/gradle.properties b/gradle.properties
index 8cadd0d..174cbc1 100644
--- a/gradle.properties
+++ b/gradle.properties
@@ -13,7 +13,7 @@ loader_version=0.17.3
 loom_version=1.13-SNAPSHOT
 
 # Mod Properties
-mod_version=2.1.0
+mod_version=2.2.0
 maven_group=blade.addon
 archives_base_name=blade-addons
 
diff --git a/src/main/java/blade/addon/Bladeaddons.java b/src/main/java/blade/addon/Bladeaddons.java
index d6c82b2..1d5d9b4 100644
--- a/src/main/java/blade/addon/Bladeaddons.java
+++ b/src/main/java/blade/addon/Bladeaddons.java
@@ -2,6 +2,7 @@ package blade.addon;
 
 import blade.addon.features.dungeon.AutoRequeue;
 import blade.addon.features.dungeon.ChestCounter;
+import blade.addon.features.dungeon.f7.BossWaypoints;
 import blade.addon.features.dungeon.f7.CrystalSpawn;
 import blade.addon.features.dungeon.DeathTickTimer;
 import blade.addon.features.dungeon.DupeClassChecker;
@@ -59,5 +60,6 @@ public class Bladeaddons implements ModInitializer {
         SecretSpawnTimer.init();
         DistanceToLedge.init();
         ItemHighlight.init();
+        BossWaypoints.init();
     }
 }
\ No newline at end of file
diff --git a/src/main/java/blade/addon/features/dungeon/AutoRequeue.java b/src/main/java/blade/addon/features/dungeon/AutoRequeue.java
index 175d35c..effa54c 100644
--- a/src/main/java/blade/addon/features/dungeon/AutoRequeue.java
+++ b/src/main/java/blade/addon/features/dungeon/AutoRequeue.java
@@ -1,12 +1,12 @@
 package blade.addon.features.dungeon;
 
 import blade.addon.utils.Location;
+import blade.addon.utils.Misc;
 import blade.addon.utils.events.Events;
 import config.practical.manager.ConfigValue;
 import net.fabricmc.fabric.api.client.message.v1.ClientReceiveMessageEvents;
 import net.minecraft.client.MinecraftClient;
 import net.minecraft.client.gui.hud.ChatHud;
-import net.minecraft.client.network.ClientPlayNetworkHandler;
 import net.minecraft.text.Text;
 
 import java.util.HashMap;
@@ -18,6 +18,7 @@ public class AutoRequeue {
     private static final Pattern PATTERN = Pattern.compile("^§9Party §8>");
     private static final Pattern DT_PATTERN = Pattern.compile("!dt");
     private static final Pattern UNDT_PATTERN = Pattern.compile("!undt");
+    private static final Pattern LEFT_PATTERN = Pattern.compile("has left the party.$");
 
     private static final int MESSAGE_OFFSET = 2;
     private static final int NO_RANK_OFFSET = 12;
@@ -28,11 +29,19 @@ public class AutoRequeue {
     @ConfigValue
     public static boolean enableAutoRequeue = false;
 
+    private static boolean someoneLeft = false;
+
     public static void init() {
         ClientReceiveMessageEvents.GAME.register((message, overlay) -> {
             if (!Location.inDungeon() && !enableAutoRequeue) return;
             String string = message.getString();
-            Matcher matcher = PATTERN.matcher(string);
+            Matcher matcher = LEFT_PATTERN.matcher(string);
+            if (matcher.find()) {
+                someoneLeft = true;
+                return;
+            }
+
+            matcher = PATTERN.matcher(string);
 
             if (!matcher.find()) return;
 
@@ -63,16 +72,13 @@ public class AutoRequeue {
         });
 
         Events.ON_RUN_END.register(() -> {
-            if (!enableAutoRequeue) return;
+            if (!enableAutoRequeue || someoneLeft) return;
 
             if (playerHashMap.isEmpty()) {
-                ClientPlayNetworkHandler networkHandler = MinecraftClient.getInstance().getNetworkHandler();
-                if (networkHandler == null) return;
-                networkHandler.sendChatCommand("instancerequeue");
+                Misc.executeCommand("instancerequeue");
             } else {
+                Misc.addChatMessage(Text.literal("Downtime reasons:"));
                 ChatHud chathud = MinecraftClient.getInstance().inGameHud.getChatHud();
-                chathud.addMessage(Text.literal("Downtime reasons:"));
-
                 playerHashMap.forEach((name, text) -> chathud.addMessage(text));
 
 
@@ -80,9 +86,8 @@ public class AutoRequeue {
         });
 
         Events.ON_LOCATION_CHANGE.register(newLocation -> {
-            if (Location.inDungeon()) {
-                playerHashMap.clear();
-            }
+            playerHashMap.clear();
+            someoneLeft = false;
         });
     }
 }
diff --git a/src/main/java/blade/addon/features/dungeon/ChestCounter.java b/src/main/java/blade/addon/features/dungeon/ChestCounter.java
index dbde5be..1c6a07e 100644
--- a/src/main/java/blade/addon/features/dungeon/ChestCounter.java
+++ b/src/main/java/blade/addon/features/dungeon/ChestCounter.java
@@ -28,19 +28,16 @@ public class ChestCounter {
     private static int chestDisplayCount = 0;
     private static int countedChests = 0;
     private static boolean hasUpdatedData = false;
-    private static boolean readPlayerEntryEvents = false;
 
     public static void init() {
         Events.ON_LOCATION_CHANGE.register(newLocation -> {
             if (Location.in(Location.DUNGEON_HUB)) {
-                readPlayerEntryEvents = true;
                 hasUpdatedData = false;
             }
         });
 
         Events.ON_PLAYER_ENTRY.register(receivedEntry -> {
-            if (!readPlayerEntryEvents || hasUpdatedData) return;
-            if (receivedEntry == null) return;
+            if (receivedEntry == null || !Location.in(Location.DUNGEON_HUB)) return;
             Text text = receivedEntry.displayName();
             if (text == null) return;
 
@@ -77,8 +74,6 @@ public class ChestCounter {
 
                 if (hasUpdatedData) {
                     drawContext.drawText(MinecraftClient.getInstance().textRenderer, Text.literal("Chests: " + Math.min(chestDisplayCount + countedChests, 60)), x, y, 0xffffffff, true);
-                } else if(readPlayerEntryEvents) {
-                    drawContext.drawText(MinecraftClient.getInstance().textRenderer, Text.literal("Updating"), x, y, 0xffffd700, true);
                 } else {
                     drawContext.drawText(MinecraftClient.getInstance().textRenderer, Text.literal("go to the dungeon hub"), x, y, 0xffff0000, true);
 
diff --git a/src/main/java/blade/addon/features/dungeon/DupeClassChecker.java b/src/main/java/blade/addon/features/dungeon/DupeClassChecker.java
index e498591..f79d615 100644
--- a/src/main/java/blade/addon/features/dungeon/DupeClassChecker.java
+++ b/src/main/java/blade/addon/features/dungeon/DupeClassChecker.java
@@ -2,6 +2,7 @@ package blade.addon.features.dungeon;
 
 import blade.addon.utils.Location;
 import blade.addon.utils.dungeon.Phase;
+import blade.addon.utils.events.Events;
 import config.practical.hud.HUDComponent;
 import config.practical.manager.ConfigValue;
 import net.fabricmc.fabric.api.client.message.v1.ClientReceiveMessageEvents;
@@ -39,6 +40,12 @@ public class DupeClassChecker {
 
             hasDuplicateClass = hasDuplicateClasses();
         });
+
+        Events.ON_LOCATION_CHANGE.register(location -> {
+            if (Location.inDungeon()) {
+                hasDuplicateClass = false;
+            }
+        });
     }
 
     private static boolean hasDuplicateClasses() {
diff --git a/src/main/java/blade/addon/features/dungeon/ExplosiveShot.java b/src/main/java/blade/addon/features/dungeon/ExplosiveShot.java
index d6c754f..7820aaf 100644
--- a/src/main/java/blade/addon/features/dungeon/ExplosiveShot.java
+++ b/src/main/java/blade/addon/features/dungeon/ExplosiveShot.java
@@ -1,10 +1,9 @@
 package blade.addon.features.dungeon;
 
 import blade.addon.utils.Location;
+import blade.addon.utils.Misc;
 import config.practical.manager.ConfigValue;
 import net.fabricmc.fabric.api.client.message.v1.ClientReceiveMessageEvents;
-import net.minecraft.client.MinecraftClient;
-import net.minecraft.client.gui.hud.InGameHud;
 import net.minecraft.text.Text;
 import net.minecraft.util.Formatting;
 
@@ -39,8 +38,7 @@ public class ExplosiveShot {
 
                     double damagePerEntity = amountNum / countNum;
 
-                    InGameHud gameHud = MinecraftClient.getInstance().inGameHud;
-                    gameHud.getChatHud().addMessage(
+                    Misc.addChatMessage(
                             Text.literal("Explosive shot did ").formatted(Formatting.GREEN)
                                     .append(Text.literal(FORMAT.format(damagePerEntity)).formatted(Formatting.YELLOW))
                                     .append(Text.literal(" damage per enemy.").formatted(Formatting.GREEN))
diff --git a/src/main/java/blade/addon/features/dungeon/MobHighlight.java b/src/main/java/blade/addon/features/dungeon/MobHighlight.java
index 7538866..90534c2 100644
--- a/src/main/java/blade/addon/features/dungeon/MobHighlight.java
+++ b/src/main/java/blade/addon/features/dungeon/MobHighlight.java
@@ -1,6 +1,7 @@
 package blade.addon.features.dungeon;
 
 import blade.addon.utils.Location;
+import blade.addon.utils.Misc;
 import blade.addon.utils.dungeon.Phase;
 import blade.addon.utils.events.Events;
 import blade.addon.utils.rendering.RenderLayers;
@@ -187,7 +188,8 @@ public class MobHighlight {
             possibleEntities.clear();
         });
 
-        WorldRenderEvents.AFTER_ENTITIES.register(MobHighlight::Render);
+        WorldRenderEvents.AFTER_ENTITIES.register(MobHighlight::renderFilled);
+        WorldRenderEvents.AFTER_ENTITIES.register(MobHighlight::renderOutline);
     }
 
     private static void testArmourStand(ArmorStandEntity armorStand) {
@@ -300,6 +302,8 @@ public class MobHighlight {
     private static boolean isARealPlayer(Entity entity) {
         if (entity instanceof PlayerEntity player) {
 
+            if (Misc.isClientPlayer(player)) return true;
+
             ClientPlayNetworkHandler networkHandler = MinecraftClient.getInstance().getNetworkHandler();
             if (networkHandler == null) return false;
 
@@ -406,8 +410,45 @@ public class MobHighlight {
         return box;
     }
 
-    private static void Render(WorldRenderContext worldRenderContext) {
+    private static void renderFilled(WorldRenderContext worldRenderContext) {
+        if (!mobHighlight || dontRenderHighlight) return;
+        if (!renderFilled()) return;
+        Camera camera = worldRenderContext.camera();
+        Vec3d cameraPos = camera.getPos();
+        MatrixStack matrixStack = worldRenderContext.matrixStack();
+        if (matrixStack == null) return;
+        matrixStack.push();
+        matrixStack.translate(-cameraPos.x, -cameraPos.y, -cameraPos.z);
+
+        VertexConsumerProvider consumers = worldRenderContext.consumers();
+        if (consumers == null) return;
+        VertexConsumer filledConsumer = consumers.getBuffer(RenderLayers.FILLED_ENTITY_LAYER);
+
+        trackedMobs.forEach((entity, mobType) -> {
+
+            if (entity.isInvisible() && MobHighlight.dontShowInvisibleMobs && entity instanceof PlayerEntity) return;
+
+            double tickProgress = worldRenderContext.tickCounter().getTickProgress(false);
+            double x = MathHelper.lerp(tickProgress, entity.lastRenderX, entity.getX());
+            double y = MathHelper.lerp(tickProgress, entity.lastRenderY, entity.getY());
+            double z = MathHelper.lerp(tickProgress, entity.lastRenderZ, entity.getZ());
+
+            Box box = getBox(entity, x, y, z);
+
+
+            int filledColor = MobHighlight.getFilledColor(entity);
+            float[] rgba = RenderUtils.toFloats(filledColor);
+            VertexRendering.drawFilledBox(matrixStack, filledConsumer, box.minX, box.minY, box.minZ, box.maxX, box.maxY, box.maxZ, rgba[0], rgba[1], rgba[2], rgba[3]);
+        });
+
+
+        matrixStack.pop();
+
+    }
+
+    private static void renderOutline(WorldRenderContext worldRenderContext) {
         if (!mobHighlight || dontRenderHighlight) return;
+        if (!renderOutline()) return;
         Camera camera = worldRenderContext.camera();
         Vec3d cameraPos = camera.getPos();
         MatrixStack matrixStack = worldRenderContext.matrixStack();
@@ -417,8 +458,7 @@ public class MobHighlight {
 
         VertexConsumerProvider consumers = worldRenderContext.consumers();
         if (consumers == null) return;
-        VertexConsumer filledBuffer = consumers.getBuffer(RenderLayers.FILLED_ENTITY_LAYER);
-        VertexConsumer outlineBuffer = consumers.getBuffer(RenderLayers.OUTLINE_ENTITY_LAYER);
+        VertexConsumer outlineConsumer = consumers.getBuffer(RenderLayers.OUTLINE_ENTITY_LAYER);
 
 
         trackedMobs.forEach((entity, mobType) -> {
@@ -432,16 +472,10 @@ public class MobHighlight {
 
             Box box = getBox(entity, x, y, z);
 
-            if (MobHighlight.renderFilled()) {
-                int filledColor = MobHighlight.getFilledColor(entity);
-                float[] rgba = RenderUtils.toFloats(filledColor);
-                VertexRendering.drawFilledBox(matrixStack, filledBuffer, box.minX, box.minY, box.minZ, box.maxX, box.maxY, box.maxZ, rgba[0], rgba[1], rgba[2], rgba[3]);
-            }
-            if (MobHighlight.renderOutline()) {
-                int outlineColor = MobHighlight.getOutlineColor(entity);
-                float[] rgba = RenderUtils.toFloats(outlineColor);
-                VertexRendering.drawBox(matrixStack, outlineBuffer, box, rgba[0], rgba[1], rgba[2], rgba[3]);
-            }
+
+            int outlineColor = MobHighlight.getOutlineColor(entity);
+            float[] rgba = RenderUtils.toFloats(outlineColor);
+            VertexRendering.drawBox(matrixStack, outlineConsumer, box, rgba[0], rgba[1], rgba[2], rgba[3]);
         });
 
 
diff --git a/src/main/java/blade/addon/features/dungeon/f7/BossWaypoints.java b/src/main/java/blade/addon/features/dungeon/f7/BossWaypoints.java
new file mode 100644
index 0000000..6de1450
--- /dev/null
+++ b/src/main/java/blade/addon/features/dungeon/f7/BossWaypoints.java
@@ -0,0 +1,263 @@
+package blade.addon.features.dungeon.f7;
+
+import blade.addon.utils.Constants;
+import blade.addon.utils.Location;
+import blade.addon.utils.Misc;
+import blade.addon.utils.Waypoint;
+import blade.addon.utils.dungeon.Phase;
+import blade.addon.utils.rendering.RenderLayers;
+import blade.addon.utils.rendering.RenderUtils;
+import com.google.gson.Gson;
+import com.google.gson.JsonArray;
+import com.google.gson.JsonElement;
+import com.google.gson.JsonObject;
+import config.practical.manager.ConfigValue;
+import net.fabricmc.fabric.api.client.rendering.v1.WorldRenderContext;
+import net.fabricmc.fabric.api.client.rendering.v1.WorldRenderEvents;
+import net.fabricmc.fabric.api.event.player.UseBlockCallback;
+import net.minecraft.client.render.Camera;
+import net.minecraft.client.render.VertexConsumer;
+import net.minecraft.client.render.VertexConsumerProvider;
+import net.minecraft.client.util.math.MatrixStack;
+import net.minecraft.entity.player.PlayerEntity;
+import net.minecraft.text.Text;
+import net.minecraft.util.ActionResult;
+import net.minecraft.util.Formatting;
+import net.minecraft.util.Hand;
+import net.minecraft.util.hit.BlockHitResult;
+import net.minecraft.util.math.BlockPos;
+import net.minecraft.util.math.Vec3d;
+import net.minecraft.world.World;
+
+import java.io.FileWriter;
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Paths;
+import java.util.concurrent.CopyOnWriteArrayList;
+
+public class BossWaypoints {
+
+    private static final Text ON = Text.literal("ON").formatted(Formatting.GREEN);
+    private static final Text OFF = Text.literal("OFF").formatted(Formatting.RED);
+
+    private static final String filePath = "./config/" + Constants.NAMESPACE + "-waypoints.json";
+    private static final String WAYPOINTS_NAME = "waypoints";
+
+    public static final CopyOnWriteArrayList<Waypoint> waypoints = new CopyOnWriteArrayList<>();
+
+    @ConfigValue
+    public static boolean enableBossWaypoints = false;
+
+    @ConfigValue
+    public static int nextWaypointColor = 0xff00F7F7;
+
+    @ConfigValue
+    public static boolean nextWaypointThroughWall = false;
+
+    private static boolean ignoreBoss = false;
+    private static boolean place = false;
+
+    public static void init() {
+        load();
+        //needs separate consumers because of depth checking or smth
+        WorldRenderEvents.BEFORE_ENTITIES.register(BossWaypoints::renderThroughWall);
+        WorldRenderEvents.BEFORE_ENTITIES.register(BossWaypoints::render);
+        UseBlockCallback.EVENT.register(BossWaypoints::onBlock);
+    }
+
+    public static boolean isInValidArea() {
+        if (!enableBossWaypoints) return false;
+        if (ignoreBoss) return true;
+
+        return Location.inDungeon() && Phase.inBoss();
+    }
+
+    public static boolean getPlace() {
+        return place;
+    }
+
+    public static void setPlace(boolean shouldPlace) {
+        place = shouldPlace;
+        Misc.addChatMessage(Text.literal("Edit mode: ").append(place ? ON : OFF));
+    }
+
+    public static void setIgnoreBoss(boolean shouldIgnoreBoss) {
+        ignoreBoss = shouldIgnoreBoss;
+        Misc.addChatMessage(Text.literal("Ignore boss: ").append(ignoreBoss ? ON : OFF));
+    }
+
+    public static void togglePlace() {
+        setPlace(!place);
+    }
+
+    public static void toggleIgnoreBoss() {
+        setIgnoreBoss(!ignoreBoss);
+    }
+
+    public static void attemptAddWaypoint(double x, double y, double z, double dx, double dy, double dz) {
+        if (waypointExists(x, y, z)) return;
+        addWaypoint(x, y, z, dx, dy, dz);
+    }
+
+    private static void addWaypoint(double x, double y, double z, double dx, double dy, double dz) {
+        float[] colorAsFloat = RenderUtils.toFloats(nextWaypointColor);
+        waypoints.add(new Waypoint(x, y, z, dx, dy, dz, colorAsFloat[0], colorAsFloat[1], colorAsFloat[2], colorAsFloat[3], nextWaypointThroughWall));
+        save();
+    }
+
+    public static Waypoint getWaypoint(double x, double y, double z) {
+        for (Waypoint waypoint : waypoints) {
+            if (waypoint.samePosition(x, y, z)) return waypoint;
+        }
+        return null;
+    }
+
+    public static void removeWaypoints(double x, double y, double z, double range) {
+        for (int i = waypoints.size() - 1; i >= 0; i--) {
+            if (waypoints.get(i).inRange(x, y, z, range)) {
+                waypoints.remove(i);
+            }
+        }
+        save();
+    }
+
+    public static void removeWaypoint(double x, double y, double z) {
+        Waypoint waypoint = getWaypoint(x, y, z);
+        if (waypoint == null) return;
+        waypoints.remove(waypoint);
+        save();
+    }
+
+    private static ActionResult onBlock(PlayerEntity playerEntity, World world, Hand hand, BlockHitResult blockHitResult) {
+        if (!isInValidArea() || !Misc.isClientPlayer(playerEntity)) return ActionResult.PASS;
+        if (hand == Hand.OFF_HAND) return ActionResult.PASS;
+
+        BlockPos pos = blockHitResult.getBlockPos();
+
+        int x = pos.getX();
+        int y = pos.getY();
+        int z = pos.getZ();
+
+        if (!place) return ActionResult.PASS;
+
+        Waypoint waypoint = getWaypoint(x, y, z);
+        if (waypoint == null) {
+            addWaypoint(pos.getX(), pos.getY(), pos.getZ(), 1, 1, 1);
+        } else {
+            waypoints.remove(waypoint);
+        }
+
+        return ActionResult.PASS;
+    }
+
+    private static void renderThroughWall(WorldRenderContext worldRenderContext) {
+        if (!isInValidArea()) return;
+
+        Camera camera = worldRenderContext.camera();
+        Vec3d cameraPos = camera.getPos();
+        MatrixStack matrixStack = worldRenderContext.matrixStack();
+        if (matrixStack == null) return;
+        matrixStack.push();
+        matrixStack.translate(-cameraPos.x, -cameraPos.y, -cameraPos.z);
+
+        VertexConsumerProvider consumers = worldRenderContext.consumers();
+        if (consumers == null) return;
+        VertexConsumer consumer = consumers.getBuffer(RenderLayers.FILLED_LAYER);
+
+        waypoints.forEach(waypoint -> {
+            if (waypoint.isThroughWall()) {
+                waypoint.Render(consumer, matrixStack);
+            }
+        });
+        matrixStack.pop();
+
+    }
+
+    private static void render(WorldRenderContext worldRenderContext) {
+        if (!isInValidArea()) return;
+
+        Camera camera = worldRenderContext.camera();
+        Vec3d cameraPos = camera.getPos();
+        MatrixStack matrixStack = worldRenderContext.matrixStack();
+        if (matrixStack == null) return;
+        matrixStack.push();
+        matrixStack.translate(-cameraPos.x, -cameraPos.y, -cameraPos.z);
+
+        VertexConsumerProvider consumers = worldRenderContext.consumers();
+        if (consumers == null) return;
+        VertexConsumer consumer = consumers.getBuffer(RenderLayers.THROUGH_WALL_FILLED_LAYER);
+
+        waypoints.forEach(waypoint -> {
+            if (!waypoint.isThroughWall()) {
+                waypoint.Render(consumer, matrixStack);
+            }
+        });
+        matrixStack.pop();
+
+    }
+
+    public static boolean waypointExists(double x, double y, double z) {
+        for (Waypoint waypoint : waypoints) {
+            if (waypoint.samePosition(x, y, z)) return true;
+        }
+        return false;
+    }
+
+    public static void save() {
+
+        JsonObject obj = new JsonObject();
+        Gson gson = new Gson();
+
+        obj.add(WAYPOINTS_NAME, gson.toJsonTree(waypoints));
+
+        JsonElement tree = gson.toJsonTree(obj);
+        try (FileWriter writer = new FileWriter(filePath)) {
+            writer.write(tree.toString());
+        } catch (IOException ignored) {
+
+        }
+    }
+
+    public static void load() {
+        String jsonContent;
+        try {
+            jsonContent = new String(Files.readAllBytes(Paths.get(filePath)));
+        } catch (IOException ignored) {
+            return;
+        }
+        Gson gson = new Gson();
+        JsonObject object = gson.fromJson(jsonContent, JsonObject.class);
+        JsonElement element = object.get(WAYPOINTS_NAME);
+
+        if (!element.isJsonArray()) return;
+        JsonArray array = element.getAsJsonArray();
+
+        for (JsonElement waypointElement : array) {
+            JsonObject waypoint = waypointElement.getAsJsonObject();
+
+            try {
+                if (!waypoint.has("x") || !waypoint.has("y") || !waypoint.has("z")
+                        || !waypoint.has("dx") || !waypoint.has("dy") || !waypoint.has("dz")
+                        || !waypoint.has("r") || !waypoint.has("g") || !waypoint.has("b") || !waypoint.has("a")
+                        || !waypoint.has("throughWall")) continue;
+
+                double x = waypoint.get("x").getAsDouble();
+                double y = waypoint.get("y").getAsDouble();
+                double z = waypoint.get("z").getAsDouble();
+                double dx = waypoint.get("dx").getAsDouble();
+                double dy = waypoint.get("dy").getAsDouble();
+                double dz = waypoint.get("dz").getAsDouble();
+
+                float r = waypoint.get("r").getAsFloat();
+                float g = waypoint.get("g").getAsFloat();
+                float b = waypoint.get("b").getAsFloat();
+                float a = waypoint.get("a").getAsFloat();
+
+                boolean throughWall = waypoint.get("throughWall").getAsBoolean();
+                waypoints.add(new Waypoint(x, y, z, dx, dy, dz, r, g, b, a, throughWall));
+            } catch (NumberFormatException e) {
+                System.out.println("msg: " + e.getMessage());
+            }
+        }
+    }
+}
diff --git a/src/main/java/blade/addon/features/dungeon/f7/InvincibilityTimer.java b/src/main/java/blade/addon/features/dungeon/f7/InvincibilityTimer.java
index 43bffb3..394f806 100644
--- a/src/main/java/blade/addon/features/dungeon/f7/InvincibilityTimer.java
+++ b/src/main/java/blade/addon/features/dungeon/f7/InvincibilityTimer.java
@@ -2,6 +2,7 @@ package blade.addon.features.dungeon.f7;
 
 import blade.addon.utils.Constants;
 import blade.addon.utils.Location;
+import blade.addon.utils.Misc;
 import blade.addon.utils.dungeon.Phase;
 import blade.addon.utils.events.Events;
 import config.practical.hud.HUDComponent;
@@ -10,6 +11,7 @@ import net.fabricmc.fabric.api.client.message.v1.ClientReceiveMessageEvents;
 import net.minecraft.client.MinecraftClient;
 import net.minecraft.client.gl.RenderPipelines;
 import net.minecraft.client.gui.DrawContext;
+import net.minecraft.item.ItemStack;
 import net.minecraft.text.Text;
 import net.minecraft.util.Formatting;
 import net.minecraft.util.Identifier;
@@ -33,8 +35,6 @@ public class InvincibilityTimer {
     private static final int SPRITE_SIZE = TEXT_HEIGHT;
 
     private static final Pattern BONZO_PATTERN = Pattern.compile("^Your (?:\\S+ )?Bonzo's Mask saved your life!$");
-    private static final Pattern SPIRIT_PATTERN = Pattern.compile("^Second Wind Activated! Your Spirit Mask saved your life!$");
-    private static final Pattern PHOENIX_PATTERN = Pattern.compile("^Your Phoenix Pet saved you from certain death!$");
 
     private static final Pattern BONZO_ON_HEAD_PATTERN = Pattern.compile("Bonzo's Mask");
     private static final Pattern SPIRIT_ON_HEAD_PATTERN = Pattern.compile("Spirit Mask");
@@ -73,6 +73,9 @@ public class InvincibilityTimer {
     @ConfigValue
     public static boolean displayInvincibilityTimer = false;
 
+    @ConfigValue
+    public static boolean showProcTitle = false;
+
     @ConfigValue
     public static DisplayWhen displayWhen = DisplayWhen.ALWAYS;
 
@@ -80,43 +83,9 @@ public class InvincibilityTimer {
     public static boolean useSprites = false;
 
     public static void init() {
-        Events.ON_SERVER_TICK.register(() -> {
-            bonzoMaskTicks = Math.max(0, bonzoMaskTicks - 1);
-            spiritMaskTicks = Math.max(0, spiritMaskTicks - 1);
-            phoenixTicks = Math.max(0, phoenixTicks - 1);
-        });
-
-        Events.ON_SLOT_CHANGE.register(((slot, item) -> {
-            if (slot == HEAD_SLOT && Location.inDungeon()) {
-                Text text = item.getCustomName();
-                if (text == null) return;
-                String string = text.getString();
-
-                Matcher matcher = BONZO_ON_HEAD_PATTERN.matcher(string);
-                bonzoMaskOn = matcher.find();
-
-                matcher = SPIRIT_ON_HEAD_PATTERN.matcher(string);
-                spiritMaskOn = matcher.find();
-
-
-            }
-        }));
-        ClientReceiveMessageEvents.GAME.register((message, overlay) -> {
-            if (!Location.inDungeon()) return;
-            String string = message.getString().replaceAll("§.", "");
-            Matcher matcher = MANUAL_EQUIP_PET_PATTERN.matcher(string);
-            if (matcher.find()) {
-                String petName = matcher.group(1);
-                phoenixOn = petName.equals("Phoenix");
-            }
-
-            matcher = RULE_EQUIP_PET_PATTERN.matcher(string);
-            if (matcher.find()) {
-                String petName = matcher.group(1);
-                phoenixOn = petName.equals("Phoenix");
-            }
-
-        });
+        Events.ON_SERVER_TICK.register(InvincibilityTimer::tick);
+        ClientReceiveMessageEvents.GAME.register(InvincibilityTimer::detectPet);
+        Events.ON_SLOT_CHANGE.register(InvincibilityTimer::detectHelmet);
 
         Events.ON_LOCATION_CHANGE.register(newLocation -> {
             if (Location.inDungeon()) {
@@ -133,20 +102,62 @@ public class InvincibilityTimer {
         });
     }
 
-    public static void parseMessage(Text message) {
+    private static void tick() {
+        bonzoMaskTicks = Math.max(0, bonzoMaskTicks - 1);
+        spiritMaskTicks = Math.max(0, spiritMaskTicks - 1);
+        phoenixTicks = Math.max(0, phoenixTicks - 1);
+    }
+
+    private static void detectPet(Text message, boolean overlay) {
+        if (!Location.inDungeon()) return;
+        String string = message.getString().replaceAll("§.", "");
+        Matcher matcher = MANUAL_EQUIP_PET_PATTERN.matcher(string);
+        if (matcher.find()) {
+            String petName = matcher.group(1);
+            phoenixOn = petName.equals("Phoenix");
+        }
+
+        matcher = RULE_EQUIP_PET_PATTERN.matcher(string);
+        if (matcher.find()) {
+            String petName = matcher.group(1);
+            phoenixOn = petName.equals("Phoenix");
+        }
+    }
+
+    private static void detectHelmet(int slot, ItemStack item) {
+        if (slot != HEAD_SLOT || !Location.inDungeon()) return;
+        Text text = item.getCustomName();
+        if (text == null) return;
+        String string = text.getString();
+
+        Matcher matcher = BONZO_ON_HEAD_PATTERN.matcher(string);
+        bonzoMaskOn = matcher.find();
+
+        matcher = SPIRIT_ON_HEAD_PATTERN.matcher(string);
+        spiritMaskOn = matcher.find();
+    }
+
+    private static void parseMessage(Text message) {
         String string = message.getString();
 
         Matcher matcher = BONZO_PATTERN.matcher(string);
         if (matcher.matches()) {
             bonzoMaskTicks = BONZO_MASK_COOLDOWN;
+            if (showProcTitle) {
+                Misc.setTitle(Text.literal("Bonzo"));
+            }
         }
-        matcher = SPIRIT_PATTERN.matcher(string);
-        if (matcher.matches()) {
+        if (string.equals("Second Wind Activated! Your Spirit Mask saved your life!")) {
             spiritMaskTicks = SPIRIT_MASK_COOLDOWN;
+            if (showProcTitle) {
+                Misc.setTitle(Text.literal("Spirit"));
+            }
         }
-        matcher = PHOENIX_PATTERN.matcher(string);
-        if (matcher.matches()) {
+        if (string.equals("Your Phoenix Pet saved you from certain death!")) {
             phoenixTicks = PHOENIX_COOLDOWN;
+            if (showProcTitle) {
+                Misc.setTitle(Text.literal("Phoenix"));
+            }
         }
     }
 
@@ -208,6 +219,10 @@ public class InvincibilityTimer {
 
     }
 
+    public static boolean spiritMaskUsed() {
+        return spiritMaskTicks > 0;
+    }
+
     @ConfigValue
     public static HUDComponent invincibilityTimer = new HUDComponent(0, 0, 110, 28, 1, "Invincibility timer",
             () -> {
diff --git a/src/main/java/blade/addon/features/dungeon/f7/RelicTimer.java b/src/main/java/blade/addon/features/dungeon/f7/RelicTimer.java
index d9c4459..72f7f06 100644
--- a/src/main/java/blade/addon/features/dungeon/f7/RelicTimer.java
+++ b/src/main/java/blade/addon/features/dungeon/f7/RelicTimer.java
@@ -2,6 +2,7 @@ package blade.addon.features.dungeon.f7;
 
 import blade.addon.utils.Constants;
 import blade.addon.utils.Location;
+import blade.addon.utils.Misc;
 import blade.addon.utils.dungeon.Phase;
 import blade.addon.utils.events.Events;
 import blade.addon.utils.rendering.RenderLayers;
@@ -15,7 +16,6 @@ import net.fabricmc.fabric.api.event.player.UseBlockCallback;
 import net.minecraft.block.BlockState;
 import net.minecraft.block.Blocks;
 import net.minecraft.client.MinecraftClient;
-import net.minecraft.client.gui.hud.InGameHud;
 import net.minecraft.client.network.ClientPlayerEntity;
 import net.minecraft.client.render.Camera;
 import net.minecraft.client.render.VertexConsumer;
@@ -159,8 +159,7 @@ public class RelicTimer {
         if (distance <= 1) {
             if (enableRelicPlaceTime) {
             double diff = (System.currentTimeMillis() - pickupTime) / 1000.0;
-                InGameHud gameHud = MinecraftClient.getInstance().inGameHud;
-                gameHud.getChatHud().addMessage(Text.literal("The ").formatted(Formatting.GREEN)
+                Misc.addChatMessage(Text.literal("The ").formatted(Formatting.GREEN)
                         .append(Text.literal(pickedupRelic.name().toLowerCase()).withColor(pickedupRelic.color))
                         .append(" relic was placed in ").formatted(Formatting.GREEN)
                         .append(Text.literal(Constants.DECIMAL_FORMAT.format(diff) + "s.").formatted(Formatting.YELLOW)));
diff --git a/src/main/java/blade/addon/features/dungeon/f7/StormTickTimer.java b/src/main/java/blade/addon/features/dungeon/f7/StormTickTimer.java
index a36fd57..ba528c8 100644
--- a/src/main/java/blade/addon/features/dungeon/f7/StormTickTimer.java
+++ b/src/main/java/blade/addon/features/dungeon/f7/StormTickTimer.java
@@ -2,6 +2,8 @@ package blade.addon.features.dungeon.f7;
 
 import blade.addon.utils.Constants;
 import blade.addon.utils.Location;
+import blade.addon.utils.Misc;
+import blade.addon.utils.dungeon.DungeonClass;
 import blade.addon.utils.dungeon.Phase;
 import blade.addon.utils.events.Events;
 import blade.addon.utils.rendering.RenderUtils;
@@ -9,7 +11,6 @@ import config.practical.hud.HUDComponent;
 import config.practical.manager.ConfigValue;
 import net.fabricmc.fabric.api.client.message.v1.ClientReceiveMessageEvents;
 import net.minecraft.client.MinecraftClient;
-import net.minecraft.client.gui.hud.InGameHud;
 import net.minecraft.text.Text;
 import net.minecraft.util.Formatting;
 
@@ -22,6 +23,7 @@ public class StormTickTimer {
 
     private static final long DEATH_DISPLAY_DURATION = 2000;
     private static final int WIDTH = 30;
+    private static final int WARN_TICK = 23 * 20;
 
     @ConfigValue
     public static boolean enableStormTickTimer = false;
@@ -29,6 +31,9 @@ public class StormTickTimer {
     @ConfigValue
     public static boolean enableStormDeathTime = false;
 
+    @ConfigValue
+    public static boolean notifyUsedSpiritMask = false;
+
     private static int tick = 0;
     private static double deathTime = 0;
     private static long deathStartDisplayTime = 0;
@@ -36,6 +41,10 @@ public class StormTickTimer {
     public static void init() {
         Events.ON_SERVER_TICK.register(() -> {
             if (Location.inDungeon() && Phase.inP2() && !Phase.stormDead()) tick++;
+
+            if (tick == WARN_TICK && notifyUsedSpiritMask && DungeonClass.isClass(DungeonClass.MAGE) && InvincibilityTimer.spiritMaskUsed()) {
+                Misc.setTitle(Text.literal("Leap to arch"));
+            }
         });
 
         Events.ON_LOCATION_CHANGE.register(newLocation -> {
@@ -54,8 +63,7 @@ public class StormTickTimer {
             if (matcher.find()) {
                 deathTime = (tick * Constants.TICK_DURATION);
                 deathStartDisplayTime = System.currentTimeMillis();
-                InGameHud gameHud = MinecraftClient.getInstance().inGameHud;
-                gameHud.getChatHud().addMessage(
+                Misc.addChatMessage(
                         Text.literal("Storm died at: ").formatted(Formatting.GREEN)
                                 .append(Text.literal(Constants.DECIMAL_FORMAT.format(deathTime) + "s").formatted(Formatting.YELLOW))
                                 .append(Text.literal(".").formatted(Formatting.GREEN))
diff --git a/src/main/java/blade/addon/mixin/FireMixin.java b/src/main/java/blade/addon/mixin/FireMixin.java
index 347a9a4..cbc6ca0 100644
--- a/src/main/java/blade/addon/mixin/FireMixin.java
+++ b/src/main/java/blade/addon/mixin/FireMixin.java
@@ -1,11 +1,11 @@
 package blade.addon.mixin;
 
 import blade.addon.features.ExtraOptions;
-import net.minecraft.client.MinecraftClient;
-import net.minecraft.client.network.ClientPlayerEntity;
+import blade.addon.utils.Misc;
 import net.minecraft.client.render.entity.EntityRenderer;
 import net.minecraft.client.render.entity.state.EntityRenderState;
 import net.minecraft.entity.Entity;
+import net.minecraft.entity.player.PlayerEntity;
 import org.spongepowered.asm.mixin.Mixin;
 import org.spongepowered.asm.mixin.injection.At;
 import org.spongepowered.asm.mixin.injection.Inject;
@@ -17,12 +17,10 @@ public class FireMixin<T extends Entity, S extends EntityRenderState> {
     @Inject(method = "updateRenderState", at = @At("TAIL"))
     public void test(T entity, S state, float tickProgress, CallbackInfo ci) {
 
-        ClientPlayerEntity clientPlayer = MinecraftClient.getInstance().player;
-        if (clientPlayer != null) {
-            if (clientPlayer == entity && ExtraOptions.hideFireInf5) {
+        if (entity instanceof PlayerEntity player && ExtraOptions.hideFireInf5) {
+            if (Misc.isClientPlayer(player)) {
                 state.onFire = false;
             }
         }
-
     }
 }
diff --git a/src/main/java/blade/addon/utils/Commands.java b/src/main/java/blade/addon/utils/Commands.java
index 0ef4cfc..8c49f2f 100644
--- a/src/main/java/blade/addon/utils/Commands.java
+++ b/src/main/java/blade/addon/utils/Commands.java
@@ -1,12 +1,16 @@
 package blade.addon.utils;
 
+import blade.addon.features.dungeon.f7.BossWaypoints;
 import blade.addon.utils.config.Config;
 import blade.addon.utils.dungeon.FillHelper;
 import com.mojang.brigadier.CommandDispatcher;
+import com.mojang.brigadier.arguments.DoubleArgumentType;
+import com.mojang.brigadier.arguments.StringArgumentType;
 import net.fabricmc.fabric.api.client.command.v2.ClientCommandManager;
 import net.fabricmc.fabric.api.client.command.v2.ClientCommandRegistrationCallback;
 import net.fabricmc.fabric.api.client.command.v2.FabricClientCommandSource;
 import net.minecraft.client.MinecraftClient;
+import net.minecraft.client.network.ClientPlayerEntity;
 import net.minecraft.command.CommandRegistryAccess;
 import org.jetbrains.annotations.NotNull;
 
@@ -31,8 +35,119 @@ public class Commands {
 
                             .then(ClientCommandManager.literal("ij").executes(context -> FillHelper.fillItem(MinecraftClient.getInstance(), FillHelper.INFLATABLE_JERRY, 64, 64)))
 
+                            .then(ClientCommandManager.literal("waypoint")
+
+                                    .then(ClientCommandManager.literal("add")
+                                            .then(ClientCommandManager.argument("x", DoubleArgumentType.doubleArg())
+                                                    .then(ClientCommandManager.argument("y", DoubleArgumentType.doubleArg())
+                                                            .then(ClientCommandManager.argument("z", DoubleArgumentType.doubleArg())
+                                                                    .executes(context -> {
+                                                                        double x = DoubleArgumentType.getDouble(context, "x");
+                                                                        double y = DoubleArgumentType.getDouble(context, "y");
+                                                                        double z = DoubleArgumentType.getDouble(context, "z");
+
+                                                                        BossWaypoints.attemptAddWaypoint(x, y, z, 1, 1, 1);
+                                                                        return Constants.SUCCESS;
+                                                                    })
+
+                                                                    .then(ClientCommandManager.argument("dx", DoubleArgumentType.doubleArg())
+                                                                            .then(ClientCommandManager.argument("dy", DoubleArgumentType.doubleArg())
+                                                                                    .then(ClientCommandManager.argument("dz", DoubleArgumentType.doubleArg())
+                                                                                            .executes(context -> {
+                                                                                                double x = DoubleArgumentType.getDouble(context, "x");
+                                                                                                double y = DoubleArgumentType.getDouble(context, "y");
+                                                                                                double z = DoubleArgumentType.getDouble(context, "z");
+                                                                                                double dx = DoubleArgumentType.getDouble(context, "dx");
+                                                                                                double dy = DoubleArgumentType.getDouble(context, "dy");
+                                                                                                double dz = DoubleArgumentType.getDouble(context, "dz");
+                                                                                                BossWaypoints.attemptAddWaypoint(x, y, z, dx, dy, dz);
+                                                                                                return Constants.SUCCESS;
+                                                                                            })
+
+
+                                                                                    )
+                                                                            )
+                                                                    )
+
+                                                            )
+                                                    )
+                                            )
+                                            .executes(context -> {
+                                                ClientPlayerEntity player = MinecraftClient.getInstance().player;
+                                                if (player == null) return 0;
+
+                                                BossWaypoints.attemptAddWaypoint(player.getBlockX(), player.getBlockY(), player.getBlockZ(), 1, 1, 1);
+                                                return Constants.SUCCESS;
+                                            })
+
+                                    )
+                                    .then(ClientCommandManager.literal("remove")
+
+                                            .then(ClientCommandManager.argument("radius", DoubleArgumentType.doubleArg())
+                                                    .executes(context -> {
+                                                        double r = DoubleArgumentType.getDouble(context, "radius");
+                                                        ClientPlayerEntity player = MinecraftClient.getInstance().player;
+                                                        if (player == null) return Constants.FAIL;
+                                                        BossWaypoints.removeWaypoints(player.getX(), player.getY(), player.getZ(), r);
+                                                        return Constants.SUCCESS;
+                                                    }))
+
+                                            .then(ClientCommandManager.argument("x", DoubleArgumentType.doubleArg())
+                                                    .then(ClientCommandManager.argument("y", DoubleArgumentType.doubleArg())
+                                                            .then(ClientCommandManager.argument("z", DoubleArgumentType.doubleArg())
+                                                                    .then(ClientCommandManager.argument("radius", DoubleArgumentType.doubleArg())
+                                                                            .executes(context -> {
+                                                                                double x = DoubleArgumentType.getDouble(context, "x");
+                                                                                double y = DoubleArgumentType.getDouble(context, "y");
+                                                                                double z = DoubleArgumentType.getDouble(context, "z");
+                                                                                double radius = DoubleArgumentType.getDouble(context, "radius");
+
+                                                                                BossWaypoints.removeWaypoints(x, y, z, radius);
+                                                                                return Constants.SUCCESS;
+                                                                            }))
+
+
+                                                                    .executes(context -> {
+                                                                        double x = DoubleArgumentType.getDouble(context, "x");
+                                                                        double y = DoubleArgumentType.getDouble(context, "y");
+                                                                        double z = DoubleArgumentType.getDouble(context, "z");
+
+                                                                        BossWaypoints.removeWaypoint(x, y, z);
+                                                                        return Constants.SUCCESS;
+                                                                    })
+                                                            )
+                                                    )
+                                            )
+
+                                    )
+
+                                    .then(ClientCommandManager.literal("editMode").executes(context -> {
+                                        BossWaypoints.togglePlace();
+                                        return Constants.SUCCESS;
+                                    }))
+
+                                    .then(ClientCommandManager.literal("ignoreBoss").executes(context -> {
+                                        BossWaypoints.toggleIgnoreBoss();
+                                        return Constants.SUCCESS;
+                                    }))
+
+                            )
+
+                            .then(ClientCommandManager.literal("debug")
+                                    .then(ClientCommandManager.argument("option", StringArgumentType.greedyString()).executes(context -> {
+                                                String option = StringArgumentType.getString(context, "option");
+                                                return Debug.parseOption(option);
+                                            })
+                                    )
+                            )
+
                             .executes(commandContext -> Scheduler.scheduleScreen(Config.createScreen(null)))
             );
+
+            dispatcher.register(ClientCommandManager.literal("dh").executes(context -> {
+                Misc.executeCommand("warp dungeon_hub");
+                return Constants.SUCCESS;
+            }));
         }
     }
 
diff --git a/src/main/java/blade/addon/utils/Constants.java b/src/main/java/blade/addon/utils/Constants.java
index 041eff5..a94d879 100644
--- a/src/main/java/blade/addon/utils/Constants.java
+++ b/src/main/java/blade/addon/utils/Constants.java
@@ -10,4 +10,7 @@ public class Constants {
     public static final int GREEN_COLOR = 0xff00ff00;
     public static final int ORANGE_COLOR = 0xffDAA06D;
     public static final int RED_COLOR = 0xffff0000;
+
+    public static final int FAIL = 0;
+    public static final int SUCCESS = 1;
 }
diff --git a/src/main/java/blade/addon/utils/Debug.java b/src/main/java/blade/addon/utils/Debug.java
new file mode 100644
index 0000000..35064f0
--- /dev/null
+++ b/src/main/java/blade/addon/utils/Debug.java
@@ -0,0 +1,28 @@
+package blade.addon.utils;
+
+import blade.addon.utils.dungeon.Phase;
+import net.minecraft.text.Text;
+
+public class Debug {
+
+    public static int parseOption(String string) {
+
+        switch (string) {
+            case "RUN_INFO":
+                sendRunInfo();
+                break;
+        }
+
+
+        return Constants.SUCCESS;
+    }
+
+    public static void sendRunInfo() {
+        Misc.addChatMessage(Text.literal("Phase: " + Phase.getPhase()));
+        Misc.addChatMessage(Text.literal("Section: " + Phase.getSection()));
+        Misc.addChatMessage(Text.literal("Gateblown: " + Phase.isGateBlownUp()));
+        Misc.addChatMessage(Text.literal("Terms done: " + Phase.isTermsDone()));
+        Misc.addChatMessage(Text.literal("In floor 7: " + Phase.isInFloor7()));
+    }
+
+}
diff --git a/src/main/java/blade/addon/utils/Misc.java b/src/main/java/blade/addon/utils/Misc.java
new file mode 100644
index 0000000..3035c64
--- /dev/null
+++ b/src/main/java/blade/addon/utils/Misc.java
@@ -0,0 +1,33 @@
+package blade.addon.utils;
+
+import net.minecraft.client.MinecraftClient;
+import net.minecraft.client.gui.hud.InGameHud;
+import net.minecraft.client.network.ClientPlayNetworkHandler;
+import net.minecraft.client.network.ClientPlayerEntity;
+import net.minecraft.entity.player.PlayerEntity;
+import net.minecraft.text.Text;
+
+public class Misc {
+    public static final MinecraftClient instance = MinecraftClient.getInstance();
+
+    public static boolean isClientPlayer(PlayerEntity entity) {
+        ClientPlayerEntity clientPlayer = instance.player;
+        if (clientPlayer == null) return false;
+        return clientPlayer == entity;
+    }
+
+    public static void addChatMessage(Text text) {
+        InGameHud gameHud = MinecraftClient.getInstance().inGameHud;
+        gameHud.getChatHud().addMessage(text);
+    }
+
+    public static void setTitle(Text text) {
+        MinecraftClient.getInstance().inGameHud.setTitle(text);
+    }
+
+    public static void executeCommand(String string) {
+        ClientPlayNetworkHandler networkHandler = MinecraftClient.getInstance().getNetworkHandler();
+        if (networkHandler == null) return;
+        networkHandler.sendChatCommand(string);
+    }
+}
diff --git a/src/main/java/blade/addon/utils/Waypoint.java b/src/main/java/blade/addon/utils/Waypoint.java
new file mode 100644
index 0000000..36688cb
--- /dev/null
+++ b/src/main/java/blade/addon/utils/Waypoint.java
@@ -0,0 +1,60 @@
+package blade.addon.utils;
+
+import net.minecraft.client.render.VertexConsumer;
+import net.minecraft.client.render.VertexRendering;
+import net.minecraft.client.util.math.MatrixStack;
+
+public class Waypoint {
+    private final double x, y, z, dx, dy, dz;
+    private final float r, g, b, a;
+    private final boolean throughWall;
+
+
+    public Waypoint(double x, double y, double z, double dx, double dy, double dz, float r, float g, float b, float a, boolean throughWall) {
+        this.x = x;
+        this.y = y;
+        this.z = z;
+        this.dx = dx;
+        this.dy = dy;
+        this.dz = dz;
+        this.r = r;
+        this.g = g;
+        this.b = b;
+        this.a = a;
+        this.throughWall = throughWall;
+    }
+
+    public boolean samePosition(double x, double y, double z) {
+        return this.x == x && this.y == y && this.z == z;
+    }
+
+    public boolean inRange(double x, double y, double z, double range) {
+        double distanceSquared = (this.x - x) * (this.x - x) + (this.y - y) * (this.y - y) + (this.z - z) * (this.z - z);
+        return distanceSquared < range * range;
+    }
+
+    public boolean isThroughWall() {
+        return throughWall;
+    }
+
+    public void Render(VertexConsumer consumer, MatrixStack matrixStack) {
+        VertexRendering.drawFilledBox(matrixStack, consumer, x, y, z, x + dx, y + dy, z + dz, r, g, b, a);
+    }
+
+    @Override
+    public String toString() {
+        return "Waypoint{" +
+                "x=" + x +
+                ", y=" + y +
+                ", z=" + z +
+                ", dx=" + dx +
+                ", dy=" + dy +
+                ", dz=" + dz +
+                ", r=" + r +
+                ", g=" + g +
+                ", b=" + b +
+                ", a=" + a +
+                ", throughWall=" + throughWall +
+                '}';
+    }
+}
diff --git a/src/main/java/blade/addon/utils/config/Config.java b/src/main/java/blade/addon/utils/config/Config.java
index c26e0c1..76d8944 100644
--- a/src/main/java/blade/addon/utils/config/Config.java
+++ b/src/main/java/blade/addon/utils/config/Config.java
@@ -3,6 +3,7 @@ package blade.addon.utils.config;
 import blade.addon.features.dungeon.AutoRequeue;
 import blade.addon.features.dungeon.ChestCounter;
 import blade.addon.features.dungeon.ItemHighlight;
+import blade.addon.features.dungeon.f7.BossWaypoints;
 import blade.addon.features.dungeon.f7.CrystalSpawn;
 import blade.addon.features.dungeon.DeathTickTimer;
 import blade.addon.features.dungeon.DupeClassChecker;
@@ -44,7 +45,8 @@ public class Config {
     public static final ConfigManager manager = new ConfigManager("./config/" + Constants.NAMESPACE + ".json",
             List.of(StormTickTimer.class, GoldorTickTimer.class, Phase.class, LeapMessage.class, PositionMessages.class, TermStartTimer.class, Split.class, DeathTickTimer.class, ExplosiveShot.class,
                     InvincibilityTimer.class, WarpCooldown.class, DupeClassChecker.class, HidePlayers.class, CrystalSpawn.class,
-                    KeyNotifier.class, RelicTimer.class, AutoRequeue.class, MobHighlight.class, ChestCounter.class, SecretSpawnTimer.class, DistanceToLedge.class, ItemHighlight.class, ExtraOptions.class));
+                    KeyNotifier.class, RelicTimer.class, AutoRequeue.class, MobHighlight.class, ChestCounter.class, SecretSpawnTimer.class, DistanceToLedge.class, ItemHighlight.class, ExtraOptions.class,
+                    BossWaypoints.class));
 
     public static Screen createScreen(Screen parent) {
         ConfigurableScreen screen = new ConfigurableScreen(TITLE, parent, manager);
@@ -60,6 +62,7 @@ public class Config {
         invincibility.add(new ConfigBool(Text.literal("Enable invincibility display"), () -> InvincibilityTimer.displayInvincibilityTimer, bool -> InvincibilityTimer.displayInvincibilityTimer = bool));
         invincibility.add(new ConfigOptions<>(Text.literal("Display when"), InvincibilityTimer.DisplayWhen.values(), () -> InvincibilityTimer.displayWhen, when -> InvincibilityTimer.displayWhen = when));
         invincibility.add(new ConfigBool(Text.literal("Use sprites"), () -> InvincibilityTimer.useSprites, bool -> InvincibilityTimer.useSprites = bool));
+        invincibility.add(new ConfigBool(Text.literal("Show title on proc"), () -> InvincibilityTimer.showProcTitle, bool -> InvincibilityTimer.showProcTitle = bool));
 
         dungeons.add(invincibility);
 
@@ -87,6 +90,12 @@ public class Config {
         screen.addCategory(dungeons);
 
         ConfigCategory floor7 = new ConfigCategory("Floor 7");
+        ConfigSection waypoints = new ConfigSection(Text.literal("Waypoints"));
+        waypoints.add(new ConfigBool(Text.literal("Enable boss waypoints"), () -> BossWaypoints.enableBossWaypoints, bool -> BossWaypoints.enableBossWaypoints = bool));
+        waypoints.add(new ConfigBool(Text.literal("Enable placing waypoints"), BossWaypoints::getPlace, BossWaypoints::setPlace));
+        waypoints.add(new ConfigColor(Text.literal("Next waypoint color"), () -> BossWaypoints.nextWaypointColor, color -> BossWaypoints.nextWaypointColor = color, "next-waypoint-color", true));
+        waypoints.add(new ConfigBool(Text.literal("Next waypoint no depth check"), () -> BossWaypoints.nextWaypointThroughWall, bool -> BossWaypoints.nextWaypointThroughWall = bool));
+        floor7.add(waypoints);
 
         ConfigSection maxor = new ConfigSection(Text.literal("Maxor"));
         maxor.add(new ConfigBool(Text.literal("Crystal Spawn Time"), () -> CrystalSpawn.enableCrystalSpawnTime, bool -> CrystalSpawn.enableCrystalSpawnTime = bool));
@@ -96,6 +105,7 @@ public class Config {
         storm.add(new ConfigBool(Text.literal("Storm Tick timer"), () -> StormTickTimer.enableStormTickTimer, bool -> StormTickTimer.enableStormTickTimer = bool));
         storm.add(new ConfigBool(Text.literal("First Death time"), () -> StormTickTimer.enableStormDeathTime, bool -> StormTickTimer.enableStormDeathTime = bool));
         storm.add(new ConfigBool(Text.literal("Distance to ledge"), () -> DistanceToLedge.displayDistanceToLedge, bool -> DistanceToLedge.displayDistanceToLedge = bool));
+        storm.add(new ConfigBool(Text.literal("Warn if spirit mask is used"), () -> StormTickTimer.notifyUsedSpiritMask, bool -> StormTickTimer.notifyUsedSpiritMask = bool));
         floor7.add(storm);
 
         ConfigSection goldor = new ConfigSection(Text.literal("Goldor"));
diff --git a/src/main/java/blade/addon/utils/dungeon/FillHelper.java b/src/main/java/blade/addon/utils/dungeon/FillHelper.java
index 8bdc0a3..bf2d886 100644
--- a/src/main/java/blade/addon/utils/dungeon/FillHelper.java
+++ b/src/main/java/blade/addon/utils/dungeon/FillHelper.java
@@ -1,5 +1,6 @@
 package blade.addon.utils.dungeon;
 
+import blade.addon.utils.Constants;
 import net.minecraft.client.MinecraftClient;
 import net.minecraft.client.network.ClientPlayerEntity;
 import net.minecraft.inventory.Inventory;
@@ -11,9 +12,6 @@ public class FillHelper {
     public static final String SUPERBOOM_TNT = "Superboom TNT";
     public static final String INFLATABLE_JERRY = "Inflatable Jerry";
 
-    private static final int FAIL = 0;
-    private static final int SUCCESS = 1;
-
     /**
      * @param client to get access to the player and networkHandler
      * @param itemName Name of item
@@ -24,7 +22,7 @@ public class FillHelper {
     public static int fillItem(MinecraftClient client, String itemName, int minThreshold, int maxCount) {
         ClientPlayerEntity player = client.player;
 
-        if (player == null) return FAIL;
+        if (player == null) return Constants.FAIL;
 
         Inventory inventory = player.getInventory();
 
@@ -41,16 +39,16 @@ public class FillHelper {
                 if (highestCount > currentCount) {
                     currentCount = highestCount;
 
-                    if (highestCount >= maxCount) return FAIL;
+                    if (highestCount >= maxCount) return Constants.FAIL;
                 }
             }
 
         }
 
-        if (minThreshold < currentCount) return FAIL;
+        if (minThreshold < currentCount) return Constants.FAIL;
 
         int itemsToGive = maxCount - currentCount;
         player.networkHandler.sendChatCommand("gfs " + itemName + " " + itemsToGive);
-        return SUCCESS;
+        return Constants.SUCCESS;
     }
 }
diff --git a/src/main/java/blade/addon/utils/dungeon/Phase.java b/src/main/java/blade/addon/utils/dungeon/Phase.java
index 3400aee..6408424 100644
--- a/src/main/java/blade/addon/utils/dungeon/Phase.java
+++ b/src/main/java/blade/addon/utils/dungeon/Phase.java
@@ -1,7 +1,9 @@
 package blade.addon.utils.dungeon;
 
+import blade.addon.utils.Constants;
 import blade.addon.utils.JsonUtility;
 import blade.addon.utils.Location;
+import blade.addon.utils.Misc;
 import blade.addon.utils.events.Events;
 import blade.addon.utils.events.interfaces.PhaseEvent;
 import blade.addon.utils.events.interfaces.RunEndEvent;
@@ -10,6 +12,7 @@ import config.practical.manager.ConfigValue;
 import net.minecraft.client.MinecraftClient;
 import net.minecraft.client.font.TextRenderer;
 import net.minecraft.text.Text;
+import net.minecraft.util.Formatting;
 
 import java.util.ArrayList;
 import java.util.HashMap;
@@ -20,11 +23,7 @@ public class Phase {
 
     private static final Pattern END_PATTERN = Pattern.compile("^\\s*☠ Defeated (.+) in 0?([\\dhms ]+)\\s*(\\(NEW RECORD!\\))?$");
     private static final Pattern SEARCH_PATTERN = Pattern.compile("^ ⏣ The Catacombs .*$");
-    private static final Pattern STORM_KILL_PATTERN = Pattern.compile("^\\[BOSS] Storm: I should have known that I stood no chance\\.$");
-    private static final Pattern GATE_BLOWN_PATTERN = Pattern.compile("^The gate has been destroyed!$");
     private static final Pattern TERMINALS_DONE_PATTERN = Pattern.compile("(activated|completed) (a terminal|a device|a lever)! \\((\\d)/(\\d)\\)$");
-    private static final Pattern CORE_OPENING_PATTERN = Pattern.compile("^The Core entrance is opening!$");
-
 
     private static final Split DUMMY_SPLIT = new Split("test split", "if this is called idk", "if this is called idk", 43690);
 
@@ -61,47 +60,47 @@ public class Phase {
 
         Events.ON_LOCATION_CHANGE.register(newLocation -> {
             if (Location.inDungeon()) {
-                currentSplits = null;
-                floor = null;
-                currentPhase = -1;
-                inFloor7 = false;
-                stormDead = false;
-                runOver = false;
-                currentSection = 0;
-                termsDone = false;
-                gateBlownUp = false;
+                reset();
             }
         });
 
-        Events.ON_GAME_MESSAGE.register(text ->  {
-            if (Location.inDungeon()) {
-                Phase.parseMessage(text);
-            }
-        });
+        Events.ON_GAME_MESSAGE.register(Phase::parseGameMessage);
+        Events.ON_TEAM.register(Phase::detectFloor);
+    }
 
-        Events.ON_TEAM.register(text -> {
-            if (!Location.inDungeon() || floor != null) return;
+    private static void detectFloor(String line) {
+        if (!Location.inDungeon() || floor != null) return;
 
-            Matcher matcher = SEARCH_PATTERN.matcher(text);
-            if (!matcher.find()) return;
-            int start = text.indexOf("(");
-            int end = text.indexOf(")");
-            floor = text.substring(start + 1, end);
+        Matcher matcher = SEARCH_PATTERN.matcher(line);
+        if (!matcher.find()) return;
+        int start = line.indexOf("(");
+        int end = line.indexOf(")");
+        floor = line.substring(start + 1, end);
 
-            currentSplits = FLOOR_SPLITS.get(floor);
-            if (currentSplits!= null) {
-                for (Split split : currentSplits) {
-                    split.reset();
-                }
-            }
-            if (floor != null) {
-                if (floor.contains("7")) inFloor7 = true;
+        currentSplits = FLOOR_SPLITS.get(floor);
+        if (currentSplits!= null) {
+            for (Split split : currentSplits) {
+                split.reset();
             }
-        });
+        }
+        if (floor != null) {
+            if (floor.contains("7")) inFloor7 = true;
+        }
     }
 
+    private static void reset() {
+        currentSplits = null;
+        floor = null;
+        currentPhase = -1;
+        inFloor7 = false;
+        stormDead = false;
+        runOver = false;
+        currentSection = 0;
+        termsDone = false;
+        gateBlownUp = false;
+    }
 
-    public static void parseMessage(Text message) {
+    public static void parseGameMessage(Text message) {
         if (!Location.inDungeon()) return;
         String string = message.getString();
         if (currentSplits == null) return;
@@ -131,59 +130,97 @@ public class Phase {
 
         Matcher matcher = END_PATTERN.matcher(string);
         if (matcher.find()) {
-            runOver = true;
-            currentPhase = currentSplits.size();
-
-            for (Split split : currentSplits) {
-                split.end();
-            }
-
-            if (Events.ON_RUN_END.hasListeners()) {
-                Events.ON_RUN_END.listeners.forEach(RunEndEvent::onRunEnd);
-            }
+           endRun();
         }
 
         if (inP2()) {
-            matcher = STORM_KILL_PATTERN.matcher(string);
-            if (matcher.find()) {
-                stormDead = true;
-                currentSection = 1;
-            }
+            
…… diff 过长已截断 ……
- fixed leap hiding only in boss not working - moved hide dead entitiesGabriel Ellnebrand · 2025-11-25 · c426f93
查看变更
src/main/java/blade/addon/Bladeaddons.java                                         |  4 ++--
 src/main/java/blade/addon/features/ExtraOptions.java                               |  3 +++
 src/main/java/blade/addon/features/dungeon/{HideEntities.java => HidePlayers.java} |  6 ++----
 src/main/java/blade/addon/mixin/RenderMixin.java                                   |  7 ++++---
 src/main/java/blade/addon/utils/config/Config.java                                 | 20 ++++++++++----------
 5 files changed, 21 insertions(+), 19 deletions(-)
diff --git a/src/main/java/blade/addon/Bladeaddons.java b/src/main/java/blade/addon/Bladeaddons.java
index 458c56e..d6c82b2 100644
--- a/src/main/java/blade/addon/Bladeaddons.java
+++ b/src/main/java/blade/addon/Bladeaddons.java
@@ -8,7 +8,7 @@ import blade.addon.features.dungeon.DupeClassChecker;
 import blade.addon.features.dungeon.ExplosiveShot;
 import blade.addon.features.dungeon.f7.DistanceToLedge;
 import blade.addon.features.dungeon.f7.GoldorTickTimer;
-import blade.addon.features.dungeon.HideEntities;
+import blade.addon.features.dungeon.HidePlayers;
 import blade.addon.features.dungeon.f7.InvincibilityTimer;
 import blade.addon.features.dungeon.KeyNotifier;
 import blade.addon.features.dungeon.LeapMessage;
@@ -48,7 +48,7 @@ public class Bladeaddons implements ModInitializer {
         LeapMessage.init();
         ExplosiveShot.init();
         DupeClassChecker.init();
-        HideEntities.init();
+        HidePlayers.init();
         CrystalSpawn.init();
         KeyNotifier.init();
         RelicTimer.init();
diff --git a/src/main/java/blade/addon/features/ExtraOptions.java b/src/main/java/blade/addon/features/ExtraOptions.java
index c83e5f4..00537b3 100644
--- a/src/main/java/blade/addon/features/ExtraOptions.java
+++ b/src/main/java/blade/addon/features/ExtraOptions.java
@@ -13,6 +13,9 @@ public class ExtraOptions {
     @ConfigValue
     public static boolean disableAbilityCooldownSound = true;
 
+    @ConfigValue
+    public static boolean hideDeadEntities = false;
+
     public static void init() {
 
     }
diff --git a/src/main/java/blade/addon/features/dungeon/HideEntities.java b/src/main/java/blade/addon/features/dungeon/HidePlayers.java
similarity index 94%
rename from src/main/java/blade/addon/features/dungeon/HideEntities.java
rename to src/main/java/blade/addon/features/dungeon/HidePlayers.java
index a649161..e0aedd6 100644
--- a/src/main/java/blade/addon/features/dungeon/HideEntities.java
+++ b/src/main/java/blade/addon/features/dungeon/HidePlayers.java
@@ -7,7 +7,7 @@ import config.practical.manager.ConfigValue;
 import net.minecraft.entity.player.PlayerEntity;
 import net.minecraft.util.math.Vec3d;
 
-public class HideEntities {
+public class HidePlayers {
 
     private static final int HIDE_DURATION = 3 * 1000;
     private static final double DISTANCE = 3;
@@ -17,9 +17,6 @@ public class HideEntities {
     private static long startTime;
     private static boolean justLeapt = false;
 
-    @ConfigValue
-    public static boolean hideDeadEntities = false;
-
     @ConfigValue
     public static boolean hideAfterLeap = false;
 
@@ -41,6 +38,7 @@ public class HideEntities {
 
     public static boolean testHideAtLeap() {
         if (!hideAfterLeap || !justLeapt) return false;
+        if (!Phase.inBoss() && hideOnlyInBoss) return false;
 
         if (System.currentTimeMillis() - startTime >= HIDE_DURATION) {
             justLeapt = false;
diff --git a/src/main/java/blade/addon/mixin/RenderMixin.java b/src/main/java/blade/addon/mixin/RenderMixin.java
index 03ef2ff..990c835 100644
--- a/src/main/java/blade/addon/mixin/RenderMixin.java
+++ b/src/main/java/blade/addon/mixin/RenderMixin.java
@@ -1,6 +1,7 @@
 package blade.addon.mixin;
 
-import blade.addon.features.dungeon.HideEntities;
+import blade.addon.features.ExtraOptions;
+import blade.addon.features.dungeon.HidePlayers;
 import blade.addon.features.dungeon.ItemHighlight;
 import net.minecraft.client.MinecraftClient;
 import net.minecraft.client.network.ClientPlayerEntity;
@@ -24,11 +25,11 @@ public class RenderMixin {
             if (clientPlayer != null) {
                 if (player.getId() == clientPlayer.getId()) return;
             }
-            cir.setReturnValue(!HideEntities.shouldHidePlayers(player));
+            cir.setReturnValue(!HidePlayers.shouldHidePlayers(player));
 
         }
 
-        if (!entity.isAlive() && HideEntities.hideDeadEntities) {
+        if (!entity.isAlive() && ExtraOptions.hideDeadEntities) {
             cir.setReturnValue(false);
         }
 
diff --git a/src/main/java/blade/addon/utils/config/Config.java b/src/main/java/blade/addon/utils/config/Config.java
index 9fc4e6e..c26e0c1 100644
--- a/src/main/java/blade/addon/utils/config/Config.java
+++ b/src/main/java/blade/addon/utils/config/Config.java
@@ -9,7 +9,7 @@ import blade.addon.features.dungeon.DupeClassChecker;
 import blade.addon.features.dungeon.ExplosiveShot;
 import blade.addon.features.dungeon.f7.DistanceToLedge;
 import blade.addon.features.dungeon.f7.GoldorTickTimer;
-import blade.addon.features.dungeon.HideEntities;
+import blade.addon.features.dungeon.HidePlayers;
 import blade.addon.features.dungeon.f7.InvincibilityTimer;
 import blade.addon.features.dungeon.KeyNotifier;
 import blade.addon.features.dungeon.LeapMessage;
@@ -43,7 +43,7 @@ public class Config {
     private static final Text TITLE = Text.literal("Blade Addons");
     public static final ConfigManager manager = new ConfigManager("./config/" + Constants.NAMESPACE + ".json",
             List.of(StormTickTimer.class, GoldorTickTimer.class, Phase.class, LeapMessage.class, PositionMessages.class, TermStartTimer.class, Split.class, DeathTickTimer.class, ExplosiveShot.class,
-                    InvincibilityTimer.class, WarpCooldown.class, DupeClassChecker.class, HideEntities.class, CrystalSpawn.class,
+                    InvincibilityTimer.class, WarpCooldown.class, DupeClassChecker.class, HidePlayers.class, CrystalSpawn.class,
                     KeyNotifier.class, RelicTimer.class, AutoRequeue.class, MobHighlight.class, ChestCounter.class, SecretSpawnTimer.class, DistanceToLedge.class, ItemHighlight.class, ExtraOptions.class));
 
     public static Screen createScreen(Screen parent) {
@@ -63,14 +63,12 @@ public class Config {
 
         dungeons.add(invincibility);
 
-        ConfigSection hideAfterLeap = new ConfigSection(Text.literal("Hide Entities"));
-        hideAfterLeap.add(new ConfigBool(Text.literal("Hide on leap"), () -> HideEntities.hideAfterLeap, bool -> HideEntities.hideAfterLeap = bool));
-        hideAfterLeap.add(new ConfigBool(Text.literal("Hide only in boss"), () -> HideEntities.hideOnlyInBoss, bool -> HideEntities.hideOnlyInBoss = bool));
-        hideAfterLeap.add(new ConfigBool(Text.literal("Hide at SS"), () -> HideEntities.hideAtSS, bool -> HideEntities.hideAtSS = bool));
-        hideAfterLeap.add(new ConfigBool(Text.literal("SS only hides before terms"), () -> HideEntities.hideBeforeTermsOnly, bool -> HideEntities.hideBeforeTermsOnly = bool));
-        hideAfterLeap.add(new ConfigBool(Text.literal("Hide dead entities"), () -> HideEntities.hideDeadEntities, bool -> HideEntities.hideDeadEntities = bool));
-
-        dungeons.add(hideAfterLeap);
+        ConfigSection hidePlayers = new ConfigSection(Text.literal("Hide Players"));
+        hidePlayers.add(new ConfigBool(Text.literal("Hide on leap"), () -> HidePlayers.hideAfterLeap, bool -> HidePlayers.hideAfterLeap = bool));
+        hidePlayers.add(new ConfigBool(Text.literal("Hide only in boss"), () -> HidePlayers.hideOnlyInBoss, bool -> HidePlayers.hideOnlyInBoss = bool));
+        hidePlayers.add(new ConfigBool(Text.literal("Hide at SS"), () -> HidePlayers.hideAtSS, bool -> HidePlayers.hideAtSS = bool));
+        hidePlayers.add(new ConfigBool(Text.literal("SS only hides before terms"), () -> HidePlayers.hideBeforeTermsOnly, bool -> HidePlayers.hideBeforeTermsOnly = bool));
+        dungeons.add(hidePlayers);
 
         ConfigSection chests = new ConfigSection(Text.literal("Croesus"));
         chests.add(new ConfigBool(Text.literal("Display current chest count"), () -> ChestCounter.displayChestCount, bool -> ChestCounter.displayChestCount = bool));
@@ -163,6 +161,8 @@ public class Config {
         extra.add(new ConfigBool(Text.literal("Disable fire in f5"), () -> ExtraOptions.hideFireInf5, bool -> ExtraOptions.hideFireInf5 = bool));
         extra.add(new ConfigBool(Text.literal("Hide arrows stuck to entities"), () -> ExtraOptions.hideStuckArrows, bool -> ExtraOptions.hideStuckArrows = bool));
         extra.add(new ConfigBool(Text.literal("Disable ability on cooldown sound"), () -> ExtraOptions.disableAbilityCooldownSound, bool -> ExtraOptions.disableAbilityCooldownSound = bool));
+        extra.add(new ConfigBool(Text.literal("Hide dead entities"), () -> ExtraOptions.hideDeadEntities, bool -> ExtraOptions.hideDeadEntities = bool));
+
         screen.addCategory(extra);