From a325d1ef10ab27ccab0b4715bdb65478fc779860 Mon Sep 17 00:00:00 2001 From: viciscat <51047087+viciscat@users.noreply.github.com> Date: Sat, 13 Dec 2025 21:12:44 +0100 Subject: [PATCH 1/6] create the record --- .../hysky/skyblocker/utils/ItemAbility.java | 96 +++++++++++++++++++ 1 file changed, 96 insertions(+) create mode 100644 src/main/java/de/hysky/skyblocker/utils/ItemAbility.java diff --git a/src/main/java/de/hysky/skyblocker/utils/ItemAbility.java b/src/main/java/de/hysky/skyblocker/utils/ItemAbility.java new file mode 100644 index 00000000000..892f559a283 --- /dev/null +++ b/src/main/java/de/hysky/skyblocker/utils/ItemAbility.java @@ -0,0 +1,96 @@ +package de.hysky.skyblocker.utils; + +import net.minecraft.item.ItemStack; +import net.minecraft.util.Formatting; +import org.apache.commons.lang3.math.NumberUtils; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.OptionalInt; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +public record ItemAbility(String name, Activation activation, OptionalInt manaCost, OptionalInt soulflowCost, OptionalInt cooldown) { + private static final Pattern ABILITY_NAME_PATTERN = Pattern.compile("Ability: (.+) " + "(" + String.join("|", Arrays.stream(Activation.values()).map(Activation::toString).toArray(String[]::new)) + ")"); + private static final Pattern MANA_COST_PATTERN = Pattern.compile("Mana Cost: (\\d+)"); + private static final Pattern SOULFLOW_COST_PATTERN = Pattern.compile("Soulflow Cost: (\\d+)"); + private static final Pattern COOLDOWN_PATTERN = Pattern.compile("Cooldown: ([0-9]+\\.?[0-9]*)s"); + public static List getAbilities(ItemStack stack) { + List strings = stack.skyblocker$getLoreStrings(); + List abilities = new ArrayList<>(2); // items rarely have more than 2 + String name = null; + int manaCost = -1; + int soulflowCost = -1; + int cooldown = -1; + Activation activation = null; + for (String string : strings) { + string = Formatting.strip(string).trim(); + Matcher matcher = ABILITY_NAME_PATTERN.matcher(string); + if (matcher.matches()) { + // add previous ability to list + if (name != null) { + abilities.add(new ItemAbility(name, activation, positiveOnly(manaCost), positiveOnly(soulflowCost), positiveOnly(cooldown))); + } + // reset values + name = matcher.group(1); + manaCost = -1; + soulflowCost = -1; + cooldown = -1; + activation = Activation.of(matcher.group(2)); + } + if (name == null) continue; + // Mana + if (manaCost < 0) { + matcher = MANA_COST_PATTERN.matcher(string); + if (matcher.matches()) { + manaCost = NumberUtils.toInt(matcher.group(1), -1); + continue; + } + } + // Soulflow + if (soulflowCost < 0) { + matcher = SOULFLOW_COST_PATTERN.matcher(string); + if (matcher.matches()) { + soulflowCost = NumberUtils.toInt(matcher.group(1), -1); + continue; + } + } + // Cooldown + if (cooldown < 0) { + matcher = COOLDOWN_PATTERN.matcher(string); + if (matcher.matches()) { + cooldown = (int) (NumberUtils.toFloat(matcher.group(1), -1) * 20); // multiply by 20 to convert to ticks. + continue; + } + } + + } + return abilities; + } + + private static OptionalInt positiveOnly(int value) { + return value < 0 ? OptionalInt.empty() : OptionalInt.of(value); + } + + public enum Activation { + RIGHT_CLICK, + LEFT_CLICK, + SNEAK_RIGHT_CLICK, + SNEAK_LEFT_CLICK; + + + + @Override + public String toString() { + return name().replace('_', ' '); + } + + public static Activation of(String name) { + for (Activation value : Activation.values()) { + if (value.toString().equals(name)) return value; + } + return RIGHT_CLICK; + } + } +} From 04e1a5e3c7fb93401c9e93c81d8a36ffa026293b Mon Sep 17 00:00:00 2001 From: viciscat <51047087+viciscat@users.noreply.github.com> Date: Sun, 14 Dec 2025 18:28:36 +0100 Subject: [PATCH 2/6] mojmap --- .../java/de/hysky/skyblocker/utils/ItemAbility.java | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/main/java/de/hysky/skyblocker/utils/ItemAbility.java b/src/main/java/de/hysky/skyblocker/utils/ItemAbility.java index 892f559a283..19065135c01 100644 --- a/src/main/java/de/hysky/skyblocker/utils/ItemAbility.java +++ b/src/main/java/de/hysky/skyblocker/utils/ItemAbility.java @@ -1,7 +1,7 @@ package de.hysky.skyblocker.utils; -import net.minecraft.item.ItemStack; -import net.minecraft.util.Formatting; +import net.minecraft.ChatFormatting; +import net.minecraft.world.item.ItemStack; import org.apache.commons.lang3.math.NumberUtils; import java.util.ArrayList; @@ -25,7 +25,7 @@ public static List getAbilities(ItemStack stack) { int cooldown = -1; Activation activation = null; for (String string : strings) { - string = Formatting.strip(string).trim(); + string = ChatFormatting.stripFormatting(string).trim(); Matcher matcher = ABILITY_NAME_PATTERN.matcher(string); if (matcher.matches()) { // add previous ability to list @@ -64,7 +64,9 @@ public static List getAbilities(ItemStack stack) { continue; } } - + } + if (name != null) { + abilities.add(new ItemAbility(name, activation, positiveOnly(manaCost), positiveOnly(soulflowCost), positiveOnly(cooldown))); } return abilities; } @@ -79,8 +81,6 @@ public enum Activation { SNEAK_RIGHT_CLICK, SNEAK_LEFT_CLICK; - - @Override public String toString() { return name().replace('_', ' '); From 23c6892dae30a0249a02432a792401e0317a7180 Mon Sep 17 00:00:00 2001 From: viciscat <51047087+viciscat@users.noreply.github.com> Date: Sun, 14 Dec 2025 19:03:29 +0100 Subject: [PATCH 3/6] use new api --- .../skyblocker/skyblock/StatusBarTracker.java | 14 ++++---------- .../skyblocker/skyblock/SwingAnimation.java | 16 +++++++--------- .../skyblock/dwarven/PickobulusHelper.java | 4 ++-- .../skyblock/teleport/PredictiveSmoothAOTE.java | 10 ++++------ .../de/hysky/skyblocker/utils/ItemAbility.java | 11 ++++++++++- 5 files changed, 27 insertions(+), 28 deletions(-) diff --git a/src/main/java/de/hysky/skyblocker/skyblock/StatusBarTracker.java b/src/main/java/de/hysky/skyblocker/skyblock/StatusBarTracker.java index 67b5046dd73..15c836c7423 100644 --- a/src/main/java/de/hysky/skyblocker/skyblock/StatusBarTracker.java +++ b/src/main/java/de/hysky/skyblocker/skyblock/StatusBarTracker.java @@ -5,6 +5,7 @@ import de.hysky.skyblocker.skyblock.fancybars.FancyStatusBars; import de.hysky.skyblocker.skyblock.fancybars.StatusBarType; import de.hysky.skyblocker.skyblock.item.PetInfo; +import de.hysky.skyblocker.utils.ItemAbility; import de.hysky.skyblocker.utils.ItemUtils; import de.hysky.skyblocker.utils.Location; import de.hysky.skyblocker.utils.RegexUtils; @@ -19,7 +20,6 @@ import net.minecraft.world.entity.player.Player; import net.minecraft.world.item.ItemStack; import net.minecraft.world.level.Level; -import java.util.Locale; import java.util.regex.Matcher; import java.util.regex.Pattern; @@ -28,7 +28,6 @@ public class StatusBarTracker { private static final Pattern DEFENSE_STATUS = Pattern.compile("§a(?[\\d,]+)§a❈ Defense *"); private static final Pattern MANA_USE = Pattern.compile("§b-([\\d,]+) Mana \\(§.*?\\) *"); private static final Pattern MANA_STATUS = Pattern.compile("§b(?[\\d,]+)/(?[\\d,]+)✎ (?:Mana|§3(?[\\d,]+)ʬ) *"); - private static final Pattern MANA_LORE = Pattern.compile("Mana Cost: (\\d+)"); private static final Minecraft client = Minecraft.getInstance(); private static Resource health = new Resource(100, 100, 0); @@ -90,16 +89,11 @@ private static InteractionResult interactItem(Player player, Level world, Intera if (client.player == null) return InteractionResult.PASS; ItemStack handStack = client.player.getMainHandItem(); int manaCost = 0; - boolean foundRightClick = false; - for (String text : handStack.skyblocker$getLoreStrings()) { - Matcher matcher; - if (foundRightClick && (matcher = MANA_LORE.matcher(text)).matches()) { - manaCost = RegexUtils.parseIntFromMatcher(matcher, 1); + for (ItemAbility ability : ItemAbility.getAbilities(handStack)) { + if (ability.activation() == ItemAbility.Activation.RIGHT_CLICK) { + manaCost = ability.manaCost().orElse(0); break; } - if (text.trim().toLowerCase(Locale.ENGLISH).endsWith("right click")) { - foundRightClick = true; - } } if (manaCost > 0 && manaCost <= mana.value()) { mana = new Resource(Math.max(mana.value() - manaCost, 0), mana.max(), mana.overflow()); diff --git a/src/main/java/de/hysky/skyblocker/skyblock/SwingAnimation.java b/src/main/java/de/hysky/skyblocker/skyblock/SwingAnimation.java index 765a155e1be..fd171285e18 100644 --- a/src/main/java/de/hysky/skyblocker/skyblock/SwingAnimation.java +++ b/src/main/java/de/hysky/skyblocker/skyblock/SwingAnimation.java @@ -1,18 +1,16 @@ package de.hysky.skyblocker.skyblock; -import java.util.Locale; -import java.util.regex.Pattern; +import java.util.List; + +import de.hysky.skyblocker.utils.ItemAbility; import net.minecraft.world.item.ItemStack; public class SwingAnimation { - private static final Pattern ABILITY = Pattern.compile("^(⦾\\s)?ability:\\s.*?right\\sclick$"); - public static boolean hasAbility(ItemStack stack) { - if (stack.isEmpty()) return false; - var lore = stack.skyblocker$getLoreStrings(); - for (var line : lore) { - if (ABILITY.matcher(line.trim().toLowerCase(Locale.ENGLISH)).matches()) - return true; + List abilities = ItemAbility.getAbilities(stack); + if (abilities.isEmpty()) return false; + for (ItemAbility ability : abilities) { + if (ability.activation() == ItemAbility.Activation.RIGHT_CLICK) return true; } return false; } diff --git a/src/main/java/de/hysky/skyblocker/skyblock/dwarven/PickobulusHelper.java b/src/main/java/de/hysky/skyblocker/skyblock/dwarven/PickobulusHelper.java index 2adb8932375..e4f571d470b 100644 --- a/src/main/java/de/hysky/skyblocker/skyblock/dwarven/PickobulusHelper.java +++ b/src/main/java/de/hysky/skyblocker/skyblock/dwarven/PickobulusHelper.java @@ -5,7 +5,7 @@ import de.hysky.skyblocker.skyblock.tabhud.util.PlayerListManager; import de.hysky.skyblocker.utils.Area; import de.hysky.skyblocker.utils.ColorUtils; -import de.hysky.skyblocker.utils.ItemUtils; +import de.hysky.skyblocker.utils.ItemAbility; import de.hysky.skyblocker.utils.Utils; import de.hysky.skyblocker.utils.render.WorldRenderExtractionCallback; import de.hysky.skyblocker.utils.render.primitive.PrimitiveCollector; @@ -148,7 +148,7 @@ private static void update() { return; } - if (ItemUtils.getLoreLineContains(CLIENT.player.getMainHandItem(), "Ability: Pickobulus") == null) { + if (!ItemAbility.hasAbility(CLIENT.player.getMainHandItem(), "Pickobulus")) { shouldRender = false; errorMessage = Component.literal("Not holding a tool with pickobulus").withStyle(ChatFormatting.RED); return; diff --git a/src/main/java/de/hysky/skyblocker/skyblock/teleport/PredictiveSmoothAOTE.java b/src/main/java/de/hysky/skyblocker/skyblock/teleport/PredictiveSmoothAOTE.java index 9783abad59c..d6b5c62df0a 100644 --- a/src/main/java/de/hysky/skyblocker/skyblock/teleport/PredictiveSmoothAOTE.java +++ b/src/main/java/de/hysky/skyblocker/skyblock/teleport/PredictiveSmoothAOTE.java @@ -7,6 +7,7 @@ import de.hysky.skyblocker.skyblock.dungeon.DungeonBoss; import de.hysky.skyblocker.skyblock.dungeon.secrets.DungeonManager; import de.hysky.skyblocker.skyblock.entity.MobGlow; +import de.hysky.skyblocker.utils.ItemAbility; import de.hysky.skyblocker.utils.ItemUtils; import de.hysky.skyblocker.utils.Location; import de.hysky.skyblocker.utils.Utils; @@ -41,14 +42,11 @@ import net.minecraft.world.phys.Vec3; import net.minecraft.world.phys.shapes.VoxelShape; import java.util.List; -import java.util.regex.Matcher; -import java.util.regex.Pattern; public class PredictiveSmoothAOTE { public static final Identifier SMOOTH_AOTE_BEFORE_PHASE = SkyblockerMod.id("smooth_aote"); private static final Minecraft CLIENT = Minecraft.getInstance(); - private static final Pattern MANA_LORE = Pattern.compile("Mana Cost: (\\d+)"); private static final long MAX_TELEPORT_TIME = 2500; //2.5 seconds private static long startTime; @@ -198,9 +196,9 @@ private static void calculateTeleportUse(InteractionHand hand) { } //make sure the player has enough mana to do the teleport - Matcher manaNeeded = ItemUtils.getLoreLineIfMatch(heldItem, MANA_LORE); - if (manaNeeded != null && manaNeeded.matches()) { - int manaCost = Integer.parseInt(manaNeeded.group(1)); + List abilities = ItemAbility.getAbilities(heldItem); + if (!abilities.isEmpty() && abilities.getFirst().manaCost().isPresent()) { + int manaCost = abilities.getFirst().manaCost().getAsInt(); int predictedMana = StatusBarTracker.getMana().value(); if (predictedMana < manaCost) { return; diff --git a/src/main/java/de/hysky/skyblocker/utils/ItemAbility.java b/src/main/java/de/hysky/skyblocker/utils/ItemAbility.java index 19065135c01..89359729884 100644 --- a/src/main/java/de/hysky/skyblocker/utils/ItemAbility.java +++ b/src/main/java/de/hysky/skyblocker/utils/ItemAbility.java @@ -12,10 +12,11 @@ import java.util.regex.Pattern; public record ItemAbility(String name, Activation activation, OptionalInt manaCost, OptionalInt soulflowCost, OptionalInt cooldown) { - private static final Pattern ABILITY_NAME_PATTERN = Pattern.compile("Ability: (.+) " + "(" + String.join("|", Arrays.stream(Activation.values()).map(Activation::toString).toArray(String[]::new)) + ")"); + private static final Pattern ABILITY_NAME_PATTERN = Pattern.compile("(?:⦾ )?Ability: (.+)" + " {2}(" + String.join("|", Arrays.stream(Activation.values()).map(Activation::toString).toArray(String[]::new)) + ")"); private static final Pattern MANA_COST_PATTERN = Pattern.compile("Mana Cost: (\\d+)"); private static final Pattern SOULFLOW_COST_PATTERN = Pattern.compile("Soulflow Cost: (\\d+)"); private static final Pattern COOLDOWN_PATTERN = Pattern.compile("Cooldown: ([0-9]+\\.?[0-9]*)s"); + public static List getAbilities(ItemStack stack) { List strings = stack.skyblocker$getLoreStrings(); List abilities = new ArrayList<>(2); // items rarely have more than 2 @@ -71,6 +72,14 @@ public static List getAbilities(ItemStack stack) { return abilities; } + public static boolean hasAbility(ItemStack stack, String ability) { + List abilities = getAbilities(stack); + for (ItemAbility itemAbility : abilities) { + if (itemAbility.name().equals(ability)) return true; + } + return false; + } + private static OptionalInt positiveOnly(int value) { return value < 0 ? OptionalInt.empty() : OptionalInt.of(value); } From e915c3172978deeca83c231698886437e81eb902 Mon Sep 17 00:00:00 2001 From: viciscat <51047087+viciscat@users.noreply.github.com> Date: Sun, 14 Dec 2025 19:35:49 +0100 Subject: [PATCH 4/6] mask to avoid parsing unnecessary things --- .../skyblocker/skyblock/StatusBarTracker.java | 2 +- .../skyblocker/skyblock/SwingAnimation.java | 2 +- .../teleport/PredictiveSmoothAOTE.java | 2 +- .../hysky/skyblocker/utils/ItemAbility.java | 21 +++++++++++++++---- 4 files changed, 20 insertions(+), 7 deletions(-) diff --git a/src/main/java/de/hysky/skyblocker/skyblock/StatusBarTracker.java b/src/main/java/de/hysky/skyblocker/skyblock/StatusBarTracker.java index 15c836c7423..f82378e8b0e 100644 --- a/src/main/java/de/hysky/skyblocker/skyblock/StatusBarTracker.java +++ b/src/main/java/de/hysky/skyblocker/skyblock/StatusBarTracker.java @@ -89,7 +89,7 @@ private static InteractionResult interactItem(Player player, Level world, Intera if (client.player == null) return InteractionResult.PASS; ItemStack handStack = client.player.getMainHandItem(); int manaCost = 0; - for (ItemAbility ability : ItemAbility.getAbilities(handStack)) { + for (ItemAbility ability : ItemAbility.getAbilities(handStack, ItemAbility.MASK_MANA_COST)) { if (ability.activation() == ItemAbility.Activation.RIGHT_CLICK) { manaCost = ability.manaCost().orElse(0); break; diff --git a/src/main/java/de/hysky/skyblocker/skyblock/SwingAnimation.java b/src/main/java/de/hysky/skyblocker/skyblock/SwingAnimation.java index fd171285e18..c1585db79dd 100644 --- a/src/main/java/de/hysky/skyblocker/skyblock/SwingAnimation.java +++ b/src/main/java/de/hysky/skyblocker/skyblock/SwingAnimation.java @@ -7,7 +7,7 @@ public class SwingAnimation { public static boolean hasAbility(ItemStack stack) { - List abilities = ItemAbility.getAbilities(stack); + List abilities = ItemAbility.getAbilities(stack, (byte) 0); if (abilities.isEmpty()) return false; for (ItemAbility ability : abilities) { if (ability.activation() == ItemAbility.Activation.RIGHT_CLICK) return true; diff --git a/src/main/java/de/hysky/skyblocker/skyblock/teleport/PredictiveSmoothAOTE.java b/src/main/java/de/hysky/skyblocker/skyblock/teleport/PredictiveSmoothAOTE.java index d6b5c62df0a..d6466348f29 100644 --- a/src/main/java/de/hysky/skyblocker/skyblock/teleport/PredictiveSmoothAOTE.java +++ b/src/main/java/de/hysky/skyblocker/skyblock/teleport/PredictiveSmoothAOTE.java @@ -196,7 +196,7 @@ private static void calculateTeleportUse(InteractionHand hand) { } //make sure the player has enough mana to do the teleport - List abilities = ItemAbility.getAbilities(heldItem); + List abilities = ItemAbility.getAbilities(heldItem, ItemAbility.MASK_MANA_COST); if (!abilities.isEmpty() && abilities.getFirst().manaCost().isPresent()) { int manaCost = abilities.getFirst().manaCost().getAsInt(); int predictedMana = StatusBarTracker.getMana().value(); diff --git a/src/main/java/de/hysky/skyblocker/utils/ItemAbility.java b/src/main/java/de/hysky/skyblocker/utils/ItemAbility.java index 89359729884..293c240538f 100644 --- a/src/main/java/de/hysky/skyblocker/utils/ItemAbility.java +++ b/src/main/java/de/hysky/skyblocker/utils/ItemAbility.java @@ -17,7 +17,16 @@ public record ItemAbility(String name, Activation activation, OptionalInt manaCo private static final Pattern SOULFLOW_COST_PATTERN = Pattern.compile("Soulflow Cost: (\\d+)"); private static final Pattern COOLDOWN_PATTERN = Pattern.compile("Cooldown: ([0-9]+\\.?[0-9]*)s"); + public static final byte MASK_MANA_COST = 1; + public static final byte MASK_SOULFLOW_COST = 2; + public static final byte MASK_COOLDOWN = 4; + public static final byte MASK_ALL = MASK_MANA_COST | MASK_SOULFLOW_COST | MASK_COOLDOWN; + public static List getAbilities(ItemStack stack) { + return getAbilities(stack, MASK_ALL); + } + + public static List getAbilities(ItemStack stack, byte mask) { List strings = stack.skyblocker$getLoreStrings(); List abilities = new ArrayList<>(2); // items rarely have more than 2 String name = null; @@ -42,7 +51,7 @@ public static List getAbilities(ItemStack stack) { } if (name == null) continue; // Mana - if (manaCost < 0) { + if (manaCost < 0 && testMask(mask, MASK_MANA_COST)) { matcher = MANA_COST_PATTERN.matcher(string); if (matcher.matches()) { manaCost = NumberUtils.toInt(matcher.group(1), -1); @@ -50,7 +59,7 @@ public static List getAbilities(ItemStack stack) { } } // Soulflow - if (soulflowCost < 0) { + if (soulflowCost < 0 && testMask(mask, MASK_SOULFLOW_COST)) { matcher = SOULFLOW_COST_PATTERN.matcher(string); if (matcher.matches()) { soulflowCost = NumberUtils.toInt(matcher.group(1), -1); @@ -58,7 +67,7 @@ public static List getAbilities(ItemStack stack) { } } // Cooldown - if (cooldown < 0) { + if (cooldown < 0 && testMask(mask, MASK_COOLDOWN)) { matcher = COOLDOWN_PATTERN.matcher(string); if (matcher.matches()) { cooldown = (int) (NumberUtils.toFloat(matcher.group(1), -1) * 20); // multiply by 20 to convert to ticks. @@ -73,7 +82,7 @@ public static List getAbilities(ItemStack stack) { } public static boolean hasAbility(ItemStack stack, String ability) { - List abilities = getAbilities(stack); + List abilities = getAbilities(stack, (byte) 0); for (ItemAbility itemAbility : abilities) { if (itemAbility.name().equals(ability)) return true; } @@ -84,6 +93,10 @@ private static OptionalInt positiveOnly(int value) { return value < 0 ? OptionalInt.empty() : OptionalInt.of(value); } + private static boolean testMask(byte input, byte mask) { + return (input & mask) != 0; + } + public enum Activation { RIGHT_CLICK, LEFT_CLICK, From e092f881601c51223c93b3775768907d7c148119 Mon Sep 17 00:00:00 2001 From: viciscat <51047087+viciscat@users.noreply.github.com> Date: Fri, 19 Dec 2025 18:47:22 +0100 Subject: [PATCH 5/6] Revert mask logic, instead inject a method in ItemStack This reverts commit ad7405ea2d5675627a4fc79a6897f557c364afa5. --- .../skyblocker/injected/SkyblockerStack.java | 5 ++++ .../skyblocker/mixins/ItemStackMixin.java | 11 ++++++++ .../skyblocker/skyblock/StatusBarTracker.java | 2 +- .../skyblocker/skyblock/SwingAnimation.java | 3 +-- .../teleport/PredictiveSmoothAOTE.java | 2 +- .../hysky/skyblocker/utils/ItemAbility.java | 25 ++++++------------- 6 files changed, 27 insertions(+), 21 deletions(-) diff --git a/src/main/java/de/hysky/skyblocker/injected/SkyblockerStack.java b/src/main/java/de/hysky/skyblocker/injected/SkyblockerStack.java index 088cf4e5347..b099bc9b986 100644 --- a/src/main/java/de/hysky/skyblocker/injected/SkyblockerStack.java +++ b/src/main/java/de/hysky/skyblocker/injected/SkyblockerStack.java @@ -2,6 +2,7 @@ import de.hysky.skyblocker.skyblock.item.PetInfo; import de.hysky.skyblocker.skyblock.item.SkyblockItemRarity; +import de.hysky.skyblocker.utils.ItemAbility; import java.util.List; @@ -27,6 +28,10 @@ default String getUuid() { return List.of(); } + default List skyblocker$getAbilities() { + return List.of(); + } + default PetInfo getPetInfo() { return PetInfo.EMPTY; } diff --git a/src/main/java/de/hysky/skyblocker/mixins/ItemStackMixin.java b/src/main/java/de/hysky/skyblocker/mixins/ItemStackMixin.java index 0295d954efd..b1767562a23 100644 --- a/src/main/java/de/hysky/skyblocker/mixins/ItemStackMixin.java +++ b/src/main/java/de/hysky/skyblocker/mixins/ItemStackMixin.java @@ -8,6 +8,7 @@ import de.hysky.skyblocker.skyblock.item.PetInfo; import de.hysky.skyblocker.skyblock.item.SkyblockItemRarity; import de.hysky.skyblocker.skyblock.profileviewer.ProfileViewerScreen; +import de.hysky.skyblocker.utils.ItemAbility; import de.hysky.skyblocker.utils.ItemUtils; import de.hysky.skyblocker.utils.OkLabColor; import de.hysky.skyblocker.utils.Utils; @@ -54,6 +55,9 @@ public abstract class ItemStackMixin implements DataComponentHolder, SkyblockerS @Unique private @Nullable List loreString; + @Unique + private @Nullable List abilities; + @Unique private @Nullable PetInfo petInfo; @@ -188,6 +192,13 @@ public String getUuid() { return loreString = ItemUtils.getLore((ItemStack) (Object) this).stream().map(Component::getString).toList(); } + @SuppressWarnings("deprecation") + @Override + public List skyblocker$getAbilities() { + if (abilities != null) return abilities; + return abilities = ItemAbility.getAbilities((ItemStack) (Object) this); + } + @SuppressWarnings("deprecation") @Override public PetInfo getPetInfo() { diff --git a/src/main/java/de/hysky/skyblocker/skyblock/StatusBarTracker.java b/src/main/java/de/hysky/skyblocker/skyblock/StatusBarTracker.java index f82378e8b0e..42f3296a084 100644 --- a/src/main/java/de/hysky/skyblocker/skyblock/StatusBarTracker.java +++ b/src/main/java/de/hysky/skyblocker/skyblock/StatusBarTracker.java @@ -89,7 +89,7 @@ private static InteractionResult interactItem(Player player, Level world, Intera if (client.player == null) return InteractionResult.PASS; ItemStack handStack = client.player.getMainHandItem(); int manaCost = 0; - for (ItemAbility ability : ItemAbility.getAbilities(handStack, ItemAbility.MASK_MANA_COST)) { + for (ItemAbility ability : handStack.skyblocker$getAbilities()) { if (ability.activation() == ItemAbility.Activation.RIGHT_CLICK) { manaCost = ability.manaCost().orElse(0); break; diff --git a/src/main/java/de/hysky/skyblocker/skyblock/SwingAnimation.java b/src/main/java/de/hysky/skyblocker/skyblock/SwingAnimation.java index c1585db79dd..751c2f39527 100644 --- a/src/main/java/de/hysky/skyblocker/skyblock/SwingAnimation.java +++ b/src/main/java/de/hysky/skyblocker/skyblock/SwingAnimation.java @@ -7,8 +7,7 @@ public class SwingAnimation { public static boolean hasAbility(ItemStack stack) { - List abilities = ItemAbility.getAbilities(stack, (byte) 0); - if (abilities.isEmpty()) return false; + List abilities = stack.skyblocker$getAbilities(); for (ItemAbility ability : abilities) { if (ability.activation() == ItemAbility.Activation.RIGHT_CLICK) return true; } diff --git a/src/main/java/de/hysky/skyblocker/skyblock/teleport/PredictiveSmoothAOTE.java b/src/main/java/de/hysky/skyblocker/skyblock/teleport/PredictiveSmoothAOTE.java index d6466348f29..7990aa87054 100644 --- a/src/main/java/de/hysky/skyblocker/skyblock/teleport/PredictiveSmoothAOTE.java +++ b/src/main/java/de/hysky/skyblocker/skyblock/teleport/PredictiveSmoothAOTE.java @@ -196,7 +196,7 @@ private static void calculateTeleportUse(InteractionHand hand) { } //make sure the player has enough mana to do the teleport - List abilities = ItemAbility.getAbilities(heldItem, ItemAbility.MASK_MANA_COST); + List abilities = heldItem.skyblocker$getAbilities(); if (!abilities.isEmpty() && abilities.getFirst().manaCost().isPresent()) { int manaCost = abilities.getFirst().manaCost().getAsInt(); int predictedMana = StatusBarTracker.getMana().value(); diff --git a/src/main/java/de/hysky/skyblocker/utils/ItemAbility.java b/src/main/java/de/hysky/skyblocker/utils/ItemAbility.java index 293c240538f..e0e9deb5308 100644 --- a/src/main/java/de/hysky/skyblocker/utils/ItemAbility.java +++ b/src/main/java/de/hysky/skyblocker/utils/ItemAbility.java @@ -17,16 +17,11 @@ public record ItemAbility(String name, Activation activation, OptionalInt manaCo private static final Pattern SOULFLOW_COST_PATTERN = Pattern.compile("Soulflow Cost: (\\d+)"); private static final Pattern COOLDOWN_PATTERN = Pattern.compile("Cooldown: ([0-9]+\\.?[0-9]*)s"); - public static final byte MASK_MANA_COST = 1; - public static final byte MASK_SOULFLOW_COST = 2; - public static final byte MASK_COOLDOWN = 4; - public static final byte MASK_ALL = MASK_MANA_COST | MASK_SOULFLOW_COST | MASK_COOLDOWN; - + /** + * Use {@link ItemStack#skyblocker$getAbilities()} + */ + @Deprecated public static List getAbilities(ItemStack stack) { - return getAbilities(stack, MASK_ALL); - } - - public static List getAbilities(ItemStack stack, byte mask) { List strings = stack.skyblocker$getLoreStrings(); List abilities = new ArrayList<>(2); // items rarely have more than 2 String name = null; @@ -51,7 +46,7 @@ public static List getAbilities(ItemStack stack, byte mask) { } if (name == null) continue; // Mana - if (manaCost < 0 && testMask(mask, MASK_MANA_COST)) { + if (manaCost < 0) { matcher = MANA_COST_PATTERN.matcher(string); if (matcher.matches()) { manaCost = NumberUtils.toInt(matcher.group(1), -1); @@ -59,7 +54,7 @@ public static List getAbilities(ItemStack stack, byte mask) { } } // Soulflow - if (soulflowCost < 0 && testMask(mask, MASK_SOULFLOW_COST)) { + if (soulflowCost < 0) { matcher = SOULFLOW_COST_PATTERN.matcher(string); if (matcher.matches()) { soulflowCost = NumberUtils.toInt(matcher.group(1), -1); @@ -67,7 +62,7 @@ public static List getAbilities(ItemStack stack, byte mask) { } } // Cooldown - if (cooldown < 0 && testMask(mask, MASK_COOLDOWN)) { + if (cooldown < 0) { matcher = COOLDOWN_PATTERN.matcher(string); if (matcher.matches()) { cooldown = (int) (NumberUtils.toFloat(matcher.group(1), -1) * 20); // multiply by 20 to convert to ticks. @@ -82,7 +77,7 @@ public static List getAbilities(ItemStack stack, byte mask) { } public static boolean hasAbility(ItemStack stack, String ability) { - List abilities = getAbilities(stack, (byte) 0); + List abilities = stack.skyblocker$getAbilities(); for (ItemAbility itemAbility : abilities) { if (itemAbility.name().equals(ability)) return true; } @@ -93,10 +88,6 @@ private static OptionalInt positiveOnly(int value) { return value < 0 ? OptionalInt.empty() : OptionalInt.of(value); } - private static boolean testMask(byte input, byte mask) { - return (input & mask) != 0; - } - public enum Activation { RIGHT_CLICK, LEFT_CLICK, From 44d8daa8d4c354cb7141506248b16b42b0f0af0a Mon Sep 17 00:00:00 2001 From: viciscat <51047087+viciscat@users.noreply.github.com> Date: Fri, 19 Dec 2025 21:06:27 +0100 Subject: [PATCH 6/6] show pickaxe ability cooldown on durability bar Also: - Changes custom durability to an OptionalDouble instead of a IntIntPair - Only run logic on items whose durability bar are rendered (previously ran on all other player and armorstand equipment slots) --- .../config/categories/MiningCategory.java | 9 ++ .../config/configs/MiningConfig.java | 2 + .../skyblocker/mixins/ItemStackMixin.java | 14 +-- .../skyblock/dwarven/PickaxeAbility.java | 87 +++++++++++++++++++ .../de/hysky/skyblocker/utils/ItemUtils.java | 44 ++++++---- .../assets/skyblocker/lang/en_us.json | 5 +- .../hysky/skyblocker/utils/ItemUtilsTest.java | 10 +-- 7 files changed, 143 insertions(+), 28 deletions(-) create mode 100644 src/main/java/de/hysky/skyblocker/skyblock/dwarven/PickaxeAbility.java diff --git a/src/main/java/de/hysky/skyblocker/config/categories/MiningCategory.java b/src/main/java/de/hysky/skyblocker/config/categories/MiningCategory.java index f23fbbb52a0..3cb2dd516be 100644 --- a/src/main/java/de/hysky/skyblocker/config/categories/MiningCategory.java +++ b/src/main/java/de/hysky/skyblocker/config/categories/MiningCategory.java @@ -32,6 +32,15 @@ public static ConfigCategory create(SkyblockerConfig defaults, SkyblockerConfig .name(Component.translatable("skyblocker.config.mining")) //Uncategorized Options + .option(Option.createBuilder() + .name(Component.translatable("skyblocker.config.mining.enablePickaxeAbility")) + .description(Component.translatable("skyblocker.config.mining.enablePickaxeAbility.@Tooltip")) + .binding(defaults.mining.enablePickaxeAbility, + () -> config.mining.enablePickaxeAbility, + newValue -> config.mining.enablePickaxeAbility = newValue) + .controller(ConfigUtils.createBooleanController()) + .build()) + .option(Option.createBuilder() .name(Component.translatable("skyblocker.config.mining.enableDrillFuel")) .description(Component.translatable("skyblocker.config.mining.enableDrillFuel.@Tooltip")) diff --git a/src/main/java/de/hysky/skyblocker/config/configs/MiningConfig.java b/src/main/java/de/hysky/skyblocker/config/configs/MiningConfig.java index 422b27cc0bb..3755cb872d5 100644 --- a/src/main/java/de/hysky/skyblocker/config/configs/MiningConfig.java +++ b/src/main/java/de/hysky/skyblocker/config/configs/MiningConfig.java @@ -6,6 +6,8 @@ import net.minecraft.client.resources.language.I18n; public class MiningConfig { + public boolean enablePickaxeAbility = false; + public boolean enableDrillFuel = true; public boolean commissionHighlight = true; diff --git a/src/main/java/de/hysky/skyblocker/mixins/ItemStackMixin.java b/src/main/java/de/hysky/skyblocker/mixins/ItemStackMixin.java index b1767562a23..75be0c81cec 100644 --- a/src/main/java/de/hysky/skyblocker/mixins/ItemStackMixin.java +++ b/src/main/java/de/hysky/skyblocker/mixins/ItemStackMixin.java @@ -12,7 +12,6 @@ import de.hysky.skyblocker.utils.ItemUtils; import de.hysky.skyblocker.utils.OkLabColor; import de.hysky.skyblocker.utils.Utils; -import it.unimi.dsi.fastutil.ints.IntIntPair; import org.jspecify.annotations.Nullable; import org.spongepowered.asm.mixin.Mixin; import org.spongepowered.asm.mixin.Unique; @@ -23,6 +22,7 @@ import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; import java.util.List; +import java.util.OptionalDouble; import java.util.function.Consumer; import net.minecraft.ChatFormatting; import net.minecraft.client.Minecraft; @@ -40,6 +40,9 @@ public abstract class ItemStackMixin implements DataComponentHolder, SkyblockerS @Unique private float durabilityBarFill = -1; + @Unique + private boolean shouldTryToProcessDurabilityFill = false; + @Unique private @Nullable String skyblockId; @@ -102,6 +105,7 @@ public abstract class ItemStackMixin implements DataComponentHolder, SkyblockerS @ModifyReturnValue(method = "isBarVisible", at = @At("RETURN")) private boolean modifyItemBarVisible(boolean original) { + shouldTryToProcessDurabilityFill = true; return original || durabilityBarFill >= 0f; } @@ -137,7 +141,7 @@ private void onInit(CallbackInfo ci) { @Unique private boolean skyblocker$shouldProcess() { // Durability bar renders atop of tooltips in ProfileViewer so disable on this screen - return !(Minecraft.getInstance() != null && Minecraft.getInstance().screen instanceof ProfileViewerScreen) && Utils.isOnSkyblock() && SkyblockerConfigManager.get().mining.enableDrillFuel && ItemUtils.hasCustomDurability((ItemStack) (Object) this); + return shouldTryToProcessDurabilityFill && !(Minecraft.getInstance().screen instanceof ProfileViewerScreen) && Utils.isOnSkyblock() && ItemUtils.hasCustomDurability((ItemStack) (Object) this); } @Unique @@ -147,14 +151,14 @@ private void onInit(CallbackInfo ci) { return; } // Calculate the durability - IntIntPair durability = ItemUtils.getDurability((ItemStack) (Object) this); + OptionalDouble durability = ItemUtils.getDurability((ItemStack) (Object) this); // Return if calculating the durability failed - if (durability == null) { + if (durability.isEmpty()) { durabilityBarFill = -1; return; } // Saves the calculated durability - durabilityBarFill = (float) durability.firstInt() / durability.secondInt(); + durabilityBarFill = (float) durability.getAsDouble(); } @SuppressWarnings("deprecation") diff --git a/src/main/java/de/hysky/skyblocker/skyblock/dwarven/PickaxeAbility.java b/src/main/java/de/hysky/skyblocker/skyblock/dwarven/PickaxeAbility.java new file mode 100644 index 00000000000..2c7f816d277 --- /dev/null +++ b/src/main/java/de/hysky/skyblocker/skyblock/dwarven/PickaxeAbility.java @@ -0,0 +1,87 @@ +package de.hysky.skyblocker.skyblock.dwarven; + +import de.hysky.skyblocker.annotations.Init; +import de.hysky.skyblocker.utils.ItemAbility; +import de.hysky.skyblocker.utils.ItemUtils; +import it.unimi.dsi.fastutil.objects.ObjectSet; +import net.fabricmc.fabric.api.client.event.lifecycle.v1.ClientTickEvents; +import net.fabricmc.fabric.api.client.message.v1.ClientReceiveMessageEvents; +import net.minecraft.client.Minecraft; +import net.minecraft.world.item.ItemStack; +import org.apache.commons.lang3.math.NumberUtils; +import org.jspecify.annotations.Nullable; + +import java.util.Objects; +import java.util.OptionalDouble; +import java.util.Set; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +public final class PickaxeAbility { + private static final Pattern COOLDOWN = Pattern.compile("Your Pickaxe ability is on cooldown for (\\d+)s"); + private static final Set PICKAXE_ABILITIES = ObjectSet.of( + "Mining Speed Boost", + "Pickobulus", + "Tunnel Vision", + "Maniac Miner", + "Gemstone Infusion", + "Sheer Force" + ); + private static @Nullable String cachedAbility; + private static int cooldown = -1; + private static int cooldownTime = -1; + + @Init + public static void init() { + ClientTickEvents.END_WORLD_TICK.register(level -> { + findPickaxeAbility(Objects.requireNonNull(Minecraft.getInstance().player).getMainHandItem()); + if (cachedAbility == null || cooldownTime >= cooldown || cooldownTime < 0) return; + cooldownTime++; + }); + ClientReceiveMessageEvents.ALLOW_GAME.register((component, overlay) -> { + if (!overlay) onChatMessage(component.getString().trim()); + return true; + }); + } + + private static void onChatMessage(String string) { + if (cachedAbility == null) return; + if (string.equals("You used your " + cachedAbility + " Pickaxe Ability!")) { + cooldownTime = 0; + return; + } else if (string.equals(cachedAbility + " is now available!")) { + cooldownTime = cooldown + 1; + } + Matcher matcher = COOLDOWN.matcher(string); + if (matcher.find()) { + int i = NumberUtils.toInt(matcher.group(1), 0); + cooldownTime = cooldown - i; + } + } + + public static boolean canHavePickaxeAbility(ItemStack stack) { + return ItemUtils.getLoreLineIf(stack, s -> s.startsWith("Breaking Power")) != null; + } + + /** + * @return empty if we know for sure the cooldown is over from the "is now available!" message. + */ + public static OptionalDouble getCooldownPercentage() { + if (cachedAbility == null || cooldownTime < 0 || cooldownTime > cooldown) return OptionalDouble.empty(); + return OptionalDouble.of((double) cooldownTime / cooldown); + } + + private static void findPickaxeAbility(ItemStack stack) { + if (!canHavePickaxeAbility(stack)) return; + for (ItemAbility ability : stack.skyblocker$getAbilities()) { + if (PICKAXE_ABILITIES.contains(ability.name()) && ability.cooldown().isPresent()) { + cachedAbility = ability.name(); + cooldown = ability.cooldown().getAsInt(); + return; + } + } + cachedAbility = null; + cooldownTime = -1; + cooldown = -1; + } +} diff --git a/src/main/java/de/hysky/skyblocker/utils/ItemUtils.java b/src/main/java/de/hysky/skyblocker/utils/ItemUtils.java index 2434efa9679..3dbb8090d72 100644 --- a/src/main/java/de/hysky/skyblocker/utils/ItemUtils.java +++ b/src/main/java/de/hysky/skyblocker/utils/ItemUtils.java @@ -11,7 +11,9 @@ import com.mojang.serialization.JsonOps; import com.mojang.serialization.codecs.RecordCodecBuilder; import de.hysky.skyblocker.SkyblockerMod; +import de.hysky.skyblocker.config.SkyblockerConfigManager; import de.hysky.skyblocker.debug.Debug; +import de.hysky.skyblocker.skyblock.dwarven.PickaxeAbility; import de.hysky.skyblocker.skyblock.hunting.Attribute; import de.hysky.skyblocker.skyblock.hunting.Attributes; import de.hysky.skyblocker.skyblock.item.PetInfo; @@ -22,7 +24,6 @@ import de.hysky.skyblocker.utils.networth.NetworthCalculator; import io.github.moulberry.repo.data.NEUItem; import it.unimi.dsi.fastutil.doubles.DoubleBooleanPair; -import it.unimi.dsi.fastutil.ints.IntIntPair; import it.unimi.dsi.fastutil.longs.LongBooleanPair; import it.unimi.dsi.fastutil.objects.Object2DoubleMap; import it.unimi.dsi.fastutil.objects.Object2ObjectMap; @@ -378,30 +379,39 @@ public static String getTimestamp(ItemStack stack) { } public static boolean hasCustomDurability(ItemStack stack) { - CompoundTag customData = getCustomData(stack); - return !customData.isEmpty() && (customData.contains("drill_fuel") || customData.getStringOr(ID, "").equals("PICKONIMBUS")); + if (SkyblockerConfigManager.get().mining.enableDrillFuel) { + CompoundTag customData = getCustomData(stack); + return !customData.isEmpty() && (customData.contains("drill_fuel") || stack.getSkyblockId().equals("PICKONIMBUS")); + } else if (SkyblockerConfigManager.get().mining.enablePickaxeAbility) { + return PickaxeAbility.canHavePickaxeAbility(stack); + } + return false; } - public static @Nullable IntIntPair getDurability(ItemStack stack) { - CompoundTag customData = getCustomData(stack); - if (customData.isEmpty()) return null; + public static OptionalDouble getDurability(ItemStack stack) { + if (SkyblockerConfigManager.get().mining.enableDrillFuel) { + CompoundTag customData = getCustomData(stack); + if (customData.isEmpty()) return OptionalDouble.empty(); - // TODO Calculate drill durability based on the drill_fuel flag, fuel_tank flag, and hotm level - // TODO Cache the max durability and only update the current durability on inventory tick + // TODO Calculate drill durability based on the drill_fuel flag, fuel_tank flag, and hotm level + // TODO Cache the max durability and only update the current durability on inventory tick - if (stack.getSkyblockId().equals("PICKONIMBUS")) { - int pickonimbusDurability = customData.getIntOr("pickonimbus_durability", 0); + if (stack.getSkyblockId().equals("PICKONIMBUS")) { + int pickonimbusDurability = customData.getIntOr("pickonimbus_durability", 0); - return IntIntPair.of(customData.contains("pickonimbus_durability") ? pickonimbusDurability : 2000, 2000); - } + return OptionalDouble.of((customData.contains("pickonimbus_durability") ? pickonimbusDurability : 2000) / 2000d); + } - String drillFuel = ChatFormatting.stripFormatting(getLoreLineIf(stack, FUEL_PREDICATE)); - if (drillFuel != null) { - String[] drillFuelStrings = NOT_DURABILITY.matcher(drillFuel).replaceAll("").trim().split("/"); - return IntIntPair.of(Integer.parseInt(drillFuelStrings[0]), Integer.parseInt(drillFuelStrings[1]) * 1000); + String drillFuel = ChatFormatting.stripFormatting(getLoreLineIf(stack, FUEL_PREDICATE)); + if (drillFuel != null) { + String[] drillFuelStrings = NOT_DURABILITY.matcher(drillFuel).replaceAll("").trim().split("/"); + return OptionalDouble.of(Integer.parseInt(drillFuelStrings[0]) / (Integer.parseInt(drillFuelStrings[1]) * 1000d)); + } + } else if (SkyblockerConfigManager.get().mining.enablePickaxeAbility) { + return PickaxeAbility.getCooldownPercentage(); } - return null; + return OptionalDouble.empty(); } /** diff --git a/src/main/resources/assets/skyblocker/lang/en_us.json b/src/main/resources/assets/skyblocker/lang/en_us.json index 2a16e816986..0575179215c 100644 --- a/src/main/resources/assets/skyblocker/lang/en_us.json +++ b/src/main/resources/assets/skyblocker/lang/en_us.json @@ -849,7 +849,10 @@ "skyblocker.config.mining.dwarvenMines.solvePuzzler": "Solve Puzzler Puzzle", "skyblocker.config.mining.enableDrillFuel": "Enable Drill Fuel", - "skyblocker.config.mining.enableDrillFuel.@Tooltip": "Shows remaining fuel with the durability bar.\nShows the remaining blocks for Pickonimbus.", + "skyblocker.config.mining.enableDrillFuel.@Tooltip": "Shows remaining fuel with the durability bar.\nShows the remaining blocks for Pickonimbus.\nTakes priority over Enable Pickaxe Ability", + + "skyblocker.config.mining.enablePickaxeAbility": "Enable Pickaxe Ability", + "skyblocker.config.mining.enablePickaxeAbility.@Tooltip": "Shows pickaxe ability cooldowns with the durability bar.", "skyblocker.config.mining.glacite": "Glacite Tunnels", "skyblocker.config.mining.glacite.autoShareCorpses": "Automatically Share Found Corpses In Chat", diff --git a/src/test/java/de/hysky/skyblocker/utils/ItemUtilsTest.java b/src/test/java/de/hysky/skyblocker/utils/ItemUtilsTest.java index 42d6c44ef0f..b7efa23d874 100644 --- a/src/test/java/de/hysky/skyblocker/utils/ItemUtilsTest.java +++ b/src/test/java/de/hysky/skyblocker/utils/ItemUtilsTest.java @@ -5,11 +5,11 @@ import com.mojang.serialization.Dynamic; import com.mojang.serialization.JsonOps; import de.hysky.skyblocker.skyblock.item.tooltip.adders.ObtainedDateTooltip; -import it.unimi.dsi.fastutil.ints.IntIntPair; import net.minecraft.SharedConstants; import net.minecraft.data.registries.VanillaRegistries; import net.minecraft.resources.RegistryOps; import net.minecraft.server.Bootstrap; +import net.minecraft.util.Mth; import net.minecraft.util.datafix.DataFixers; import net.minecraft.util.datafix.fixes.References; import net.minecraft.world.item.ItemStack; @@ -17,6 +17,7 @@ import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; +import java.util.OptionalDouble; import java.util.TimeZone; public class ItemUtilsTest { @@ -123,9 +124,8 @@ void testGetTimestamp() { @Test void testGetDurability() { - IntIntPair durability = ItemUtils.getDurability(TITANIUM_DRILL_DR_X655); - Assertions.assertNotNull(durability); - Assertions.assertEquals(5395, durability.leftInt()); - Assertions.assertEquals(10_000, durability.rightInt()); + OptionalDouble durability = ItemUtils.getDurability(TITANIUM_DRILL_DR_X655); + Assertions.assertTrue(durability.isPresent()); + Assertions.assertTrue(Mth.equal(durability.getAsDouble(), 0.5395)); } }