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 9e010e3f6a6..6b67699a897 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 134cdf9f819..18a3b002eee 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; } @@ -138,7 +142,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 @@ -148,14 +152,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 e6b8e99775d..5b8675f6d49 100644 --- a/src/main/java/de/hysky/skyblocker/utils/ItemUtils.java +++ b/src/main/java/de/hysky/skyblocker/utils/ItemUtils.java @@ -11,8 +11,10 @@ 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.ChestValue; +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; @@ -23,7 +25,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; @@ -408,30 +409,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 2b736ff249f..85fc5c2595e 100644 --- a/src/main/resources/assets/skyblocker/lang/en_us.json +++ b/src/main/resources/assets/skyblocker/lang/en_us.json @@ -874,7 +874,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)); } }