diff --git a/build.gradle b/build.gradle index 375f80e116..43d554afaf 100644 --- a/build.gradle +++ b/build.gradle @@ -159,12 +159,12 @@ dependencies { modImplementation "com.terraformersmc:modmenu:${project.mod_menu_version}" // REI - modCompileOnly "me.shedaniel:RoughlyEnoughItems-api-fabric:${project.rei_version}" - //modRuntimeOnly "me.shedaniel:RoughlyEnoughItems-fabric:${project.rei_version}" +// modCompileOnly "me.shedaniel:RoughlyEnoughItems-api-fabric:${project.rei_version}" + modImplementation "me.shedaniel:RoughlyEnoughItems-fabric:${project.rei_version}" // EMI - modCompileOnly "dev.emi:emi-fabric:${project.emi_version}:api" - //modLocalRuntime "dev.emi:emi-fabric:${project.emi_version}" +// modCompileOnly "dev.emi:emi-fabric:${project.emi_version}:api" + modImplementation "dev.emi:emi-fabric:${project.emi_version}" // JEI (Using modrinth repo since official release is in mojmap and doesn't work) modCompileOnly "maven.modrinth:jei:${project.jei_version}-fabric" @@ -286,4 +286,4 @@ publishing { // The repositories here will be used for publishing your artifact, not for // retrieving dependencies. } -} \ No newline at end of file +} diff --git a/gradle.properties b/gradle.properties index e3dd7ed85a..cbb8b8dec1 100644 --- a/gradle.properties +++ b/gradle.properties @@ -21,7 +21,7 @@ mod_menu_version = 12.0.0-beta.1 ## REI (https://modrinth.com/mod/rei/versions?l=fabric) rei_version = 17.0.789 ## EMI (https://modrinth.com/mod/emi/versions) -emi_version = 1.1.10+1.21 +emi_version = 1.1.16+1.21.1 ## JEI (https://modrinth.com/mod/jei/versions) jei_version = 19.8.4.113 diff --git a/src/main/java/de/hysky/skyblocker/injected/AlignedText.java b/src/main/java/de/hysky/skyblocker/injected/AlignedText.java new file mode 100644 index 0000000000..4ea84939ef --- /dev/null +++ b/src/main/java/de/hysky/skyblocker/injected/AlignedText.java @@ -0,0 +1,61 @@ +package de.hysky.skyblocker.injected; + +import net.minecraft.text.MutableText; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +public interface AlignedText { + /** + *

+ * Aligned Text + *

+ *

+ * This method is used to display text at a certain x offset after the current text in tooltips. + * This allows for aligned text when used on multiple rows with the same offset. + *

+ *

+ * This method can be chained to achieve a grid-like layout in tooltips. + *

+ *

+ * Styling + *

+ *

+ * The way styling applies to aligned text is slightly different from normal text, where the styling of the parent text is applied to children as well + * (which causes almost all uses of text with formatting to be appended on an empty parent text when there is more than 1 style in the same line). + *

+ *

+ * For aligned text, each node has their own formatting and there is no style inheritance between them. + *

+ *

+ * However, each aligned text node can still have their own children elements like normal text, + * where the children will inherit the style of the parent and their text content will be appended to the parent. + *

+ * + * @param text The text to render after this text + * @param xOffset The x offset to apply to the given {@code text}, + * relative to the start of the text object this method is called upon. + * @return The {@code text} object passed in, for chaining purposes + */ + default @NotNull MutableText align(@NotNull MutableText text, int xOffset) { + return text; + } + + default @Nullable MutableText getAlignedText() { + return null; + } + + /** + * @return The x offset to apply to the text, or {@link Integer#MIN_VALUE } if there's no aligned text or 0 if first of the chain + */ + default int getXOffset() { + return Integer.MIN_VALUE; + } + + default void setXOffset(int xOffset) {} + + default MutableText getFirstOfChain() { + return null; + } + + default void setFirstOfChain(MutableText text) {} +} diff --git a/src/main/java/de/hysky/skyblocker/mixins/DrawContextMixin.java b/src/main/java/de/hysky/skyblocker/mixins/DrawContextMixin.java index 56a5183a91..b58db8e42c 100644 --- a/src/main/java/de/hysky/skyblocker/mixins/DrawContextMixin.java +++ b/src/main/java/de/hysky/skyblocker/mixins/DrawContextMixin.java @@ -11,8 +11,8 @@ @Mixin(DrawContext.class) public abstract class DrawContextMixin { - @ModifyExpressionValue(method = "drawCooldownProgress", at = @At(value = "INVOKE", target = "Lnet/minecraft/entity/player/ItemCooldownManager;getCooldownProgress(Lnet/minecraft/item/ItemStack;F)F")) - private float skyblocker$modifyItemCooldown(float cooldownProgress, @Local(argsOnly = true) ItemStack stack) { - return Utils.isOnSkyblock() && ItemCooldowns.isOnCooldown(stack) ? ItemCooldowns.getItemCooldownEntry(stack).getRemainingCooldownPercent() : cooldownProgress; - } + @ModifyExpressionValue(method = "drawCooldownProgress", at = @At(value = "INVOKE", target = "Lnet/minecraft/entity/player/ItemCooldownManager;getCooldownProgress(Lnet/minecraft/item/ItemStack;F)F")) + private float skyblocker$modifyItemCooldown(float cooldownProgress, @Local(argsOnly = true) ItemStack stack) { + return Utils.isOnSkyblock() && ItemCooldowns.isOnCooldown(stack) ? ItemCooldowns.getItemCooldownEntry(stack).getRemainingCooldownPercent() : cooldownProgress; + } } diff --git a/src/main/java/de/hysky/skyblocker/mixins/MutableTextMixin.java b/src/main/java/de/hysky/skyblocker/mixins/MutableTextMixin.java new file mode 100644 index 0000000000..993eb02123 --- /dev/null +++ b/src/main/java/de/hysky/skyblocker/mixins/MutableTextMixin.java @@ -0,0 +1,106 @@ +package de.hysky.skyblocker.mixins; + +import com.llamalad7.mixinextras.injector.wrapoperation.Operation; +import com.llamalad7.mixinextras.injector.wrapoperation.WrapOperation; +import de.hysky.skyblocker.injected.AlignedText; +import de.hysky.skyblocker.utils.render.gui.AlignedOrderedText; +import net.minecraft.text.MutableText; +import net.minecraft.text.OrderedText; +import net.minecraft.text.StringVisitable; +import net.minecraft.text.Text; +import net.minecraft.util.Language; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Unique; +import org.spongepowered.asm.mixin.injection.At; + +import java.util.ArrayList; +import java.util.List; + +@Mixin(MutableText.class) +public abstract class MutableTextMixin implements AlignedText, Text { + //There's an implicit linked list here, where each text has a reference to the next one. + //With the addition of firstOfChain it becomes a doubly linked list with the caveat of the reference of each text being the first of the chain rather than the previous element. + //There's no need for a real doubly linked list with references to the previous elements, as the only operation that needs to be done is to get the first element of the chain to render the whole chain properly. + @Unique + @Nullable + private MutableText alignedWith = null; + @Unique + private int xOffset = Integer.MIN_VALUE; + + // Null if this is the first of the chain, not null otherwise & always points to the first of the aligned text chain + @Unique + private MutableText firstOfChain = null; + + @Unique + Logger logger = LoggerFactory.getLogger("Skyblocker Mutable Text"); + + @Override + public @NotNull MutableText align(@NotNull MutableText text, int xOffset) { + this.alignedWith = text; + if (firstOfChain == null) { + text.setFirstOfChain((MutableText) (Object) this); + text.setXOffset(xOffset); + this.xOffset = 0; + } else { + text.setFirstOfChain(firstOfChain); + text.setXOffset(xOffset); + } + return text; + } + + @Override + public @Nullable MutableText getAlignedText() { + return alignedWith; + } + + @Override + public int getXOffset() { + return xOffset; + } + + @Override + public void setXOffset(int xOffset) { + this.xOffset = xOffset; + } + + @Override + public MutableText getFirstOfChain() { + return firstOfChain; + } + + @Override + public void setFirstOfChain(MutableText text) { + firstOfChain = text; + } + + @WrapOperation(method = "asOrderedText", at = @At(target = "Lnet/minecraft/util/Language;reorder(Lnet/minecraft/text/StringVisitable;)Lnet/minecraft/text/OrderedText;", value = "INVOKE")) + private OrderedText skyblocker$asOrderedText(Language instance, StringVisitable visitable, Operation original) { + if (visitable instanceof MutableText mutableText) { + MutableText tmp = mutableText.getFirstOfChain(); + if (tmp != null) { + List segments = new ArrayList<>(); + while (tmp != null) { + segments.add(new AlignedOrderedText.Segment(original.call(instance, tmp), tmp.getXOffset())); + tmp = tmp.getAlignedText(); + } + return new AlignedOrderedText(segments); + } else { // This is the first of the chain + tmp = mutableText.getAlignedText(); + if (tmp != null) { + List segments = new ArrayList<>(); + segments.add(new AlignedOrderedText.Segment(original.call(instance, mutableText), 0)); + while (tmp != null) { + segments.add(new AlignedOrderedText.Segment(original.call(instance, tmp), tmp.getXOffset())); + tmp = tmp.getAlignedText(); + } + return new AlignedOrderedText(segments); + } + } + } + return original.call(instance, visitable); + } +} diff --git a/src/main/java/de/hysky/skyblocker/mixins/TextHandlerMixin.java b/src/main/java/de/hysky/skyblocker/mixins/TextHandlerMixin.java new file mode 100644 index 0000000000..e6e35894bd --- /dev/null +++ b/src/main/java/de/hysky/skyblocker/mixins/TextHandlerMixin.java @@ -0,0 +1,63 @@ +package de.hysky.skyblocker.mixins; + +import com.llamalad7.mixinextras.injector.wrapoperation.Operation; +import com.llamalad7.mixinextras.injector.wrapoperation.WrapOperation; +import com.llamalad7.mixinextras.sugar.Local; +import de.hysky.skyblocker.utils.render.gui.AlignedOrderedText; +import net.minecraft.client.font.TextHandler; +import net.minecraft.text.*; +import org.apache.commons.lang3.mutable.MutableFloat; +import org.spongepowered.asm.mixin.Final; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Shadow; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; + +import java.util.List; +import java.util.function.BiConsumer; + +@Mixin(TextHandler.class) +public abstract class TextHandlerMixin { + @Final + @Shadow + TextHandler.WidthRetriever widthRetriever; + + @WrapOperation(method = "getWidth(Lnet/minecraft/text/OrderedText;)F", at = @At(value = "INVOKE", target = "Lnet/minecraft/text/OrderedText;accept(Lnet/minecraft/text/CharacterVisitor;)Z")) + private boolean skyblocker$getWidth(OrderedText instance, CharacterVisitor characterVisitor, Operation original, @Local MutableFloat mutableFloat) { + if (instance instanceof AlignedOrderedText alignedOrderedText) { + MutableFloat width = new MutableFloat(); + List segments = alignedOrderedText.segments(); + float tmp = 0; + for (AlignedOrderedText.Segment segment : segments) { + tmp = segment.xOffset(); + if (width.addAndGet(-tmp) < 0) width.setValue(tmp); // If the offset is greater than the current width, set the width to the offset to not allow clipping between segments + segment.text().accept((index, style, codePoint) -> { // This is a copied version of the original operation, but the width addition is done to our MutableFloat rather than the original + width.add(this.widthRetriever.getWidth(codePoint, style)); + return true; + }); + } + + mutableFloat.setValue(width.getValue()); + return true; + } + return original.call(instance, characterVisitor); + } + + @Inject(method = "wrapLines(Lnet/minecraft/text/StringVisitable;ILnet/minecraft/text/Style;Ljava/util/function/BiConsumer;)V", at = @At("HEAD"), cancellable = true) + private void skyblocker$wrapLines(StringVisitable text, int maxWidth, Style style, BiConsumer lineConsumer, CallbackInfo ci) { + if (text instanceof MutableText mutableText) { + switch (mutableText.getXOffset()) { + case 0 -> { + lineConsumer.accept(mutableText, false); + ci.cancel(); + } + case Integer.MIN_VALUE -> {} + default -> { + lineConsumer.accept(mutableText.getFirstOfChain(), false); + ci.cancel(); + } + } + } + } +} diff --git a/src/main/java/de/hysky/skyblocker/mixins/jei/RenderHelperMixin.java b/src/main/java/de/hysky/skyblocker/mixins/jei/RenderHelperMixin.java new file mode 100644 index 0000000000..b0889fa691 --- /dev/null +++ b/src/main/java/de/hysky/skyblocker/mixins/jei/RenderHelperMixin.java @@ -0,0 +1,22 @@ +package de.hysky.skyblocker.mixins.jei; + +import com.llamalad7.mixinextras.injector.wrapoperation.Operation; +import com.llamalad7.mixinextras.injector.wrapoperation.WrapOperation; +import mezz.jei.fabric.platform.RenderHelper; +import net.minecraft.client.font.TextRenderer; +import net.minecraft.text.MutableText; +import net.minecraft.text.OrderedText; +import net.minecraft.text.StringVisitable; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.injection.At; + +import java.util.List; + +@Mixin(value = RenderHelper.class, remap = false) +public abstract class RenderHelperMixin { + @WrapOperation(method = "lambda$renderTooltip$0", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/font/TextRenderer;wrapLines(Lnet/minecraft/text/StringVisitable;I)Ljava/util/List;"), require = 0) + private static List skyblocker$renderTooltip(TextRenderer instance, StringVisitable text, int width, Operation> original) { + if (text instanceof MutableText mutableText && mutableText.getXOffset() != Integer.MIN_VALUE) return List.of(mutableText.asOrderedText()); + return original.call(instance, text, width); + } +} diff --git a/src/main/java/de/hysky/skyblocker/mixins/rei/ScreenOverlayImplFabricMixin.java b/src/main/java/de/hysky/skyblocker/mixins/rei/ScreenOverlayImplFabricMixin.java new file mode 100644 index 0000000000..dc37887dd5 --- /dev/null +++ b/src/main/java/de/hysky/skyblocker/mixins/rei/ScreenOverlayImplFabricMixin.java @@ -0,0 +1,23 @@ +package de.hysky.skyblocker.mixins.rei; + +import com.llamalad7.mixinextras.injector.wrapoperation.Operation; +import com.llamalad7.mixinextras.injector.wrapoperation.WrapOperation; +import me.shedaniel.rei.impl.client.gui.fabric.ScreenOverlayImplFabric; +import net.minecraft.client.font.TextHandler; +import net.minecraft.text.MutableText; +import net.minecraft.text.StringVisitable; +import net.minecraft.text.Style; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.injection.At; + +import java.util.List; + +@Mixin(value = ScreenOverlayImplFabric.class) +public class ScreenOverlayImplFabricMixin { + @WrapOperation(method = "lambda$renderTooltipInner$0", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/font/TextHandler;wrapLines(Lnet/minecraft/text/StringVisitable;ILnet/minecraft/text/Style;)Ljava/util/List;"), require = 0) + private static List renderTooltipInner(TextHandler instance, StringVisitable text, int maxWidth, Style style, Operation> original) { + if (text instanceof MutableText mutableText && mutableText.getXOffset() != Integer.MIN_VALUE) return List.of(); + + return original.call(instance, text, maxWidth, style); + } +} diff --git a/src/main/java/de/hysky/skyblocker/skyblock/item/tooltip/ItemTooltip.java b/src/main/java/de/hysky/skyblocker/skyblock/item/tooltip/ItemTooltip.java index c21fd0a14c..a424010255 100644 --- a/src/main/java/de/hysky/skyblocker/skyblock/item/tooltip/ItemTooltip.java +++ b/src/main/java/de/hysky/skyblocker/skyblock/item/tooltip/ItemTooltip.java @@ -12,6 +12,7 @@ import de.hysky.skyblocker.utils.scheduler.Scheduler; import net.minecraft.client.MinecraftClient; import net.minecraft.item.ItemStack; +import net.minecraft.text.MutableText; import net.minecraft.text.Text; import net.minecraft.util.Formatting; import org.slf4j.Logger; @@ -43,11 +44,11 @@ public static void nullWarning() { } } - public static Text getCoinsMessage(double price, int count) { + public static MutableText getCoinsMessage(double price, int count) { return getCoinsMessage(price, count, false); } - public static Text getCoinsMessage(double price, int count, boolean preCounted) { + public static MutableText getCoinsMessage(double price, int count, boolean preCounted) { // Format the price string once String priceString = String.format(Locale.ENGLISH, "%1$,.1f", preCounted ? price / count : price); diff --git a/src/main/java/de/hysky/skyblocker/skyblock/item/tooltip/adders/AccessoryTooltip.java b/src/main/java/de/hysky/skyblocker/skyblock/item/tooltip/adders/AccessoryTooltip.java index 7c540a68c0..4df0634af6 100644 --- a/src/main/java/de/hysky/skyblocker/skyblock/item/tooltip/adders/AccessoryTooltip.java +++ b/src/main/java/de/hysky/skyblocker/skyblock/item/tooltip/adders/AccessoryTooltip.java @@ -6,7 +6,6 @@ import it.unimi.dsi.fastutil.Pair; import net.minecraft.item.ItemStack; import net.minecraft.screen.slot.Slot; -import net.minecraft.text.MutableText; import net.minecraft.text.Text; import net.minecraft.util.Formatting; import org.jetbrains.annotations.Nullable; @@ -25,20 +24,18 @@ public void addToTooltip(@Nullable Slot focusedSlot, ItemStack stack, List Pair report = AccessoriesHelper.calculateReport4Accessory(internalID); if (report.left() != AccessoriesHelper.AccessoryReport.INELIGIBLE) { - MutableText title = Text.literal(String.format("%-19s", "Accessory: ")).withColor(0xf57542); - Text stateText = switch (report.left()) { - case HAS_HIGHEST_TIER -> Text.literal("✔ Collected").formatted(Formatting.GREEN); - case IS_GREATER_TIER -> Text.literal("✦ Upgrade ").withColor(0x218bff).append(Text.literal(report.right()).withColor(0xf8f8ff)); - case HAS_GREATER_TIER -> Text.literal("↑ Upgradable ").withColor(0xf8d048).append(Text.literal(report.right()).withColor(0xf8f8ff)); - case OWNS_BETTER_TIER -> Text.literal("↓ Downgrade ").formatted(Formatting.GRAY).append(Text.literal(report.right()).withColor(0xf8f8ff)); - case MISSING -> Text.literal("✖ Missing ").formatted(Formatting.RED).append(Text.literal(report.right()).withColor(0xf8f8ff)); - - //Should never be the case - default -> Text.literal("? Unknown").formatted(Formatting.GRAY); - }; - - lines.add(title.append(stateText)); + lines.add(Text.literal("Accessory:").withColor(0xf57542).align( + switch (report.left()) { + case HAS_HIGHEST_TIER -> Text.literal("✔ Collected").formatted(Formatting.GREEN); + case IS_GREATER_TIER -> Text.literal("✦ Upgrade ").withColor(0x218bff).append(Text.literal(report.right()).withColor(0xf8f8ff)); + case HAS_GREATER_TIER -> Text.literal("↑ Upgradable ").withColor(0xf8d048).append(Text.literal(report.right()).withColor(0xf8f8ff)); + case OWNS_BETTER_TIER -> Text.literal("↓ Downgrade ").formatted(Formatting.GRAY).append(Text.literal(report.right()).withColor(0xf8f8ff)); + case MISSING -> Text.literal("✖ Missing ").formatted(Formatting.RED).append(Text.literal(report.right()).withColor(0xf8f8ff)); + + //Should never be the case + default -> Text.literal("? Unknown").formatted(Formatting.GRAY); + }, 100)); } } } diff --git a/src/main/java/de/hysky/skyblocker/skyblock/item/tooltip/adders/AvgBinTooltip.java b/src/main/java/de/hysky/skyblocker/skyblock/item/tooltip/adders/AvgBinTooltip.java index 827fee74bf..80f8fa98d7 100644 --- a/src/main/java/de/hysky/skyblocker/skyblock/item/tooltip/adders/AvgBinTooltip.java +++ b/src/main/java/de/hysky/skyblocker/skyblock/item/tooltip/adders/AvgBinTooltip.java @@ -23,7 +23,7 @@ public void addToTooltip(@Nullable Slot focusedSlot, ItemStack stack, List String neuName = stack.getNeuName(); Average type = ItemTooltip.config.avg; - if ((TooltipInfoType.ONE_DAY_AVERAGE.getData() == null && type != Average.THREE_DAY) || (TooltipInfoType.THREE_DAY_AVERAGE.getData() == null && type != Average.ONE_DAY)) { + if ((TooltipInfoType.ONE_DAY_AVERAGE.getData() == null && type != Average.THREE_DAY) || (TooltipInfoType.THREE_DAY_AVERAGE.getData() == null && type != Average.ONE_DAY)) { ItemTooltip.nullWarning(); } else { /* @@ -35,21 +35,24 @@ public void addToTooltip(@Nullable Slot focusedSlot, ItemStack stack, List // "No data" line because of API not keeping old data, it causes NullPointerException if (type == Average.ONE_DAY || type == Average.BOTH) { lines.add( - Text.literal(String.format("%-19s", "1 Day Avg. Price:")) + Text.literal("1 Day Avg. Price:") .formatted(Formatting.GOLD) - .append(!TooltipInfoType.ONE_DAY_AVERAGE.getData().containsKey(neuName) - ? Text.literal("No data").formatted(Formatting.RED) - : ItemTooltip.getCoinsMessage(TooltipInfoType.ONE_DAY_AVERAGE.getData().getDouble(neuName), stack.getCount()) + + .align(TooltipInfoType.ONE_DAY_AVERAGE.getData().containsKey(neuName) + ? ItemTooltip.getCoinsMessage(TooltipInfoType.ONE_DAY_AVERAGE.getData().getDouble(neuName), stack.getCount()) + : Text.literal("No data").formatted(Formatting.RED), + 100 ) ); } if (type == Average.THREE_DAY || type == Average.BOTH) { lines.add( - Text.literal(String.format("%-19s", "3 Day Avg. Price:")) + Text.literal("3 Day Avg. Price:") .formatted(Formatting.GOLD) - .append(!TooltipInfoType.THREE_DAY_AVERAGE.getData().containsKey(neuName) - ? Text.literal("No data").formatted(Formatting.RED) - : ItemTooltip.getCoinsMessage(TooltipInfoType.THREE_DAY_AVERAGE.getData().getDouble(neuName), stack.getCount()) + .align(TooltipInfoType.THREE_DAY_AVERAGE.getData().containsKey(neuName) + ? ItemTooltip.getCoinsMessage(TooltipInfoType.THREE_DAY_AVERAGE.getData().getDouble(neuName), stack.getCount()) + : Text.literal("No data").formatted(Formatting.RED), + 100 ) ); } diff --git a/src/main/java/de/hysky/skyblocker/skyblock/item/tooltip/adders/BazaarPriceTooltip.java b/src/main/java/de/hysky/skyblocker/skyblock/item/tooltip/adders/BazaarPriceTooltip.java index 4e1c0dc303..bb519d5b91 100644 --- a/src/main/java/de/hysky/skyblocker/skyblock/item/tooltip/adders/BazaarPriceTooltip.java +++ b/src/main/java/de/hysky/skyblocker/skyblock/item/tooltip/adders/BazaarPriceTooltip.java @@ -14,36 +14,39 @@ import java.util.List; public class BazaarPriceTooltip extends SimpleTooltipAdder { - public BazaarPriceTooltip(int priority) { + public BazaarPriceTooltip(int priority) { super(priority); } @Override public void addToTooltip(@Nullable Slot focusedSlot, ItemStack stack, List lines) { - String skyblockApiId = stack.getSkyblockApiId(); + String skyblockApiId = stack.getSkyblockApiId(); + if (!TooltipInfoType.BAZAAR.hasOrNullWarning(skyblockApiId)) return; - if (TooltipInfoType.BAZAAR.hasOrNullWarning(skyblockApiId)) { - int count; - if (lines.size() >= 4 && lines.get(3).getSiblings().size() >= 2 && lines.get(1).getString().endsWith("Sack")) { - //The count is in the 2nd sibling of the 3rd line of the lore. here V - //Example line: empty[style={color=dark_purple,!italic}, siblings=[literal{Stored: }[style={color=gray}], literal{0}[style={color=dark_gray}], literal{/20k}[style={color=gray}]] - String line = lines.get(3).getSiblings().get(1).getString().replace(",", ""); - count = NumberUtils.isParsable(line) && !line.equals("0") ? Integer.parseInt(line) : stack.getCount(); - } else { - count = stack.getCount(); - } - BazaarProduct product = TooltipInfoType.BAZAAR.getData().get(skyblockApiId); - lines.add(Text.literal(String.format("%-18s", "Bazaar buy Price:")) - .formatted(Formatting.GOLD) - .append(product.buyPrice().isEmpty() - ? Text.literal("No data").formatted(Formatting.RED) - : ItemTooltip.getCoinsMessage(product.buyPrice().getAsDouble(), count))); - lines.add(Text.literal(String.format("%-19s", "Bazaar sell Price:")) - .formatted(Formatting.GOLD) - .append(product.sellPrice().isEmpty() - ? Text.literal("No data").formatted(Formatting.RED) - : ItemTooltip.getCoinsMessage(product.sellPrice().getAsDouble(), count))); + int count; + if (lines.size() >= 4 && lines.get(3).getSiblings().size() >= 2 && lines.get(1).getString().endsWith("Sack")) { + //The count is in the 2nd sibling of the 3rd line of the lore. here V + //Example line: empty[style={color=dark_purple,!italic}, siblings=[literal{Stored: }[style={color=gray}], literal{0}[style={color=dark_gray}], literal{/20k}[style={color=gray}]] + String line = lines.get(3).getSiblings().get(1).getString().replace(",", ""); + count = NumberUtils.isParsable(line) && !line.equals("0") ? Integer.parseInt(line) : stack.getCount(); + } else { + count = stack.getCount(); } + + @SuppressWarnings("DataFlowIssue") //The existence of the data is already checked via hasOrNullWarning, so the data is guaranteed to be present + BazaarProduct product = TooltipInfoType.BAZAAR.getData().get(skyblockApiId); + lines.add(Text.literal("Bazaar buy Price:") + .formatted(Formatting.GOLD) + .align(product.buyPrice().isEmpty() + ? Text.literal("No data").formatted(Formatting.RED) + : ItemTooltip.getCoinsMessage(product.buyPrice().getAsDouble(), count), + 100)); + lines.add(Text.literal("Bazaar sell Price:") + .formatted(Formatting.GOLD) + .align(product.sellPrice().isEmpty() + ? Text.literal("No data").formatted(Formatting.RED) + : ItemTooltip.getCoinsMessage(product.sellPrice().getAsDouble(), count), + 100)); } @Override diff --git a/src/main/java/de/hysky/skyblocker/skyblock/item/tooltip/adders/CraftPriceTooltip.java b/src/main/java/de/hysky/skyblocker/skyblock/item/tooltip/adders/CraftPriceTooltip.java index 943e855dca..d1d8da8ce1 100644 --- a/src/main/java/de/hysky/skyblocker/skyblock/item/tooltip/adders/CraftPriceTooltip.java +++ b/src/main/java/de/hysky/skyblocker/skyblock/item/tooltip/adders/CraftPriceTooltip.java @@ -59,8 +59,9 @@ public void addToTooltip(@Nullable Slot focusedSloFt, ItemStack stack, List - lines.add(Text.literal(String.format("%-20s", "Crafting Price:")).formatted(Formatting.GOLD) - .append(ItemTooltip.getCoinsMessage(totalCraftCost / outputIngredient.getAmount(), amountInStack)))); + lines.add(Text.literal("Crafting Price:") + .formatted(Formatting.GOLD) + .align(ItemTooltip.getCoinsMessage(totalCraftCost / outputIngredient.getAmount(), amountInStack), 100))); } catch (Exception e) { LOGGER.error("[Skyblocker Craft Price] Error calculating craftprice tooltip for: " + stack.getNeuName(), e); diff --git a/src/main/java/de/hysky/skyblocker/skyblock/item/tooltip/adders/DungeonQualityTooltip.java b/src/main/java/de/hysky/skyblocker/skyblock/item/tooltip/adders/DungeonQualityTooltip.java index f6efc2f2af..605f05edf3 100644 --- a/src/main/java/de/hysky/skyblocker/skyblock/item/tooltip/adders/DungeonQualityTooltip.java +++ b/src/main/java/de/hysky/skyblocker/skyblock/item/tooltip/adders/DungeonQualityTooltip.java @@ -20,22 +20,26 @@ public DungeonQualityTooltip(int priority) { @Override public void addToTooltip(@Nullable Slot focusedSlot, ItemStack stack, List lines) { NbtCompound customData = ItemUtils.getCustomData(stack); - if (customData == null || !customData.contains("baseStatBoostPercentage")) return; + if (customData.isEmpty() || !customData.contains("baseStatBoostPercentage")) return; int baseStatBoostPercentage = customData.getInt("baseStatBoostPercentage"); boolean maxQuality = baseStatBoostPercentage == 50; - if (maxQuality) { - lines.add(Text.literal(String.format("%-17s", "Item Quality:") + baseStatBoostPercentage + "/50").formatted(Formatting.RED).formatted(Formatting.BOLD)); - } else { - lines.add(Text.literal(String.format("%-21s", "Item Quality:") + baseStatBoostPercentage + "/50").formatted(Formatting.BLUE)); - } + lines.add(Text.literal("Item Quality:").formatted(Formatting.BLUE) + .align(maxQuality + ? Text.literal(baseStatBoostPercentage + "/50") + .formatted(Formatting.RED, Formatting.BOLD) + : Text.literal(baseStatBoostPercentage + "/50") + .formatted(Formatting.BLUE), + 100)); if (customData.contains("item_tier")) { // sometimes it just isn't here? int itemTier = customData.getInt("item_tier"); - if (maxQuality) { - lines.add(Text.literal(String.format("%-17s", "Floor Tier:") + itemTier + " (" + getItemTierFloor(itemTier) + ")").formatted(Formatting.RED).formatted(Formatting.BOLD)); - } else { - lines.add(Text.literal(String.format("%-21s", "Floor Tier:") + itemTier + " (" + getItemTierFloor(itemTier) + ")").formatted(Formatting.BLUE)); - } + lines.add(Text.literal("Floor Tier:").formatted(Formatting.BLUE) + .align(maxQuality + ? Text.literal(itemTier + " (" + getItemTierFloor(itemTier) + ")") + .formatted(Formatting.RED, Formatting.BOLD) + : Text.literal(itemTier + " (" + getItemTierFloor(itemTier) + ")") + .formatted(Formatting.BLUE), + 100)); } } diff --git a/src/main/java/de/hysky/skyblocker/skyblock/item/tooltip/adders/EssenceShopPrice.java b/src/main/java/de/hysky/skyblocker/skyblock/item/tooltip/adders/EssenceShopPrice.java index e4a85ec1dd..1c7b3e11df 100644 --- a/src/main/java/de/hysky/skyblocker/skyblock/item/tooltip/adders/EssenceShopPrice.java +++ b/src/main/java/de/hysky/skyblocker/skyblock/item/tooltip/adders/EssenceShopPrice.java @@ -58,8 +58,8 @@ public void addToTooltip(@Nullable Slot focusedSlot, ItemStack stack, List if (priceData == 0) return; //Default value for getLong is 0 if no value exists for that key lines.add(Text.empty() - .append(Text.literal("Essence Cost: ").formatted(Formatting.AQUA)) - .append(Text.literal(DECIMAL_FORMAT.format(priceData * cost.getAsLong()) + " coins").formatted(Formatting.DARK_AQUA)) + .append(Text.literal("Essence Cost:").formatted(Formatting.AQUA)) + .align(Text.literal(DECIMAL_FORMAT.format(priceData * cost.getAsLong()) + " coins").formatted(Formatting.DARK_AQUA), 100) .append(Text.literal(" (").formatted(Formatting.GRAY)) .append(Text.literal(DECIMAL_FORMAT.format(priceData) + " each").formatted(Formatting.GRAY)) .append(Text.literal(")").formatted(Formatting.GRAY)) diff --git a/src/main/java/de/hysky/skyblocker/skyblock/item/tooltip/adders/EstimatedItemValueTooltip.java b/src/main/java/de/hysky/skyblocker/skyblock/item/tooltip/adders/EstimatedItemValueTooltip.java index 3009fedb81..864ba990da 100644 --- a/src/main/java/de/hysky/skyblocker/skyblock/item/tooltip/adders/EstimatedItemValueTooltip.java +++ b/src/main/java/de/hysky/skyblocker/skyblock/item/tooltip/adders/EstimatedItemValueTooltip.java @@ -1,9 +1,5 @@ package de.hysky.skyblocker.skyblock.item.tooltip.adders; -import java.util.List; - -import org.jetbrains.annotations.Nullable; - import de.hysky.skyblocker.skyblock.item.tooltip.ItemTooltip; import de.hysky.skyblocker.skyblock.item.tooltip.SimpleTooltipAdder; import de.hysky.skyblocker.skyblock.item.tooltip.info.TooltipInfoType; @@ -13,6 +9,9 @@ import net.minecraft.screen.slot.Slot; import net.minecraft.text.Text; import net.minecraft.util.Formatting; +import org.jetbrains.annotations.Nullable; + +import java.util.List; public class EstimatedItemValueTooltip extends SimpleTooltipAdder { @@ -25,9 +24,9 @@ public void addToTooltip(@Nullable Slot focusedSlot, ItemStack stack, List NetworthResult result = NetworthCalculator.getItemNetworth(stack); if (result.price() > 0) { - lines.add(Text.literal(String.format("%-20s", "Est. Item Value:")) - .formatted(Formatting.GOLD) - .append(ItemTooltip.getCoinsMessage(result.price(), stack.getCount(), true))); + lines.add(Text.literal("Est. Item Value:") + .formatted(Formatting.GOLD) + .align(ItemTooltip.getCoinsMessage(result.price(), stack.getCount(), true), 100)); } } diff --git a/src/main/java/de/hysky/skyblocker/skyblock/item/tooltip/adders/LBinTooltip.java b/src/main/java/de/hysky/skyblocker/skyblock/item/tooltip/adders/LBinTooltip.java index 8aabc13ff8..cb86671ad3 100644 --- a/src/main/java/de/hysky/skyblocker/skyblock/item/tooltip/adders/LBinTooltip.java +++ b/src/main/java/de/hysky/skyblocker/skyblock/item/tooltip/adders/LBinTooltip.java @@ -27,9 +27,10 @@ public void addToTooltip(@Nullable Slot focusedSlot, ItemStack stack, List // Check for whether the item exist in bazaar price data, because Skytils keeps some bazaar item data in lbin api if (TooltipInfoType.LOWEST_BINS.hasOrNullWarning(skyblockApiId) && !TooltipInfoType.BAZAAR.hasOrNullWarning(skyblockApiId)) { - lines.add(Text.literal(String.format("%-19s", "Lowest BIN Price:")) + lines.add(Text.literal("Lowest BIN Price:") .formatted(Formatting.GOLD) - .append(ItemTooltip.getCoinsMessage(TooltipInfoType.LOWEST_BINS.getData().getDouble(skyblockApiId), stack.getCount()))); + .align(ItemTooltip.getCoinsMessage(TooltipInfoType.LOWEST_BINS.getData().getDouble(skyblockApiId), stack.getCount()), + 100)); } } } diff --git a/src/main/java/de/hysky/skyblocker/skyblock/item/tooltip/adders/MotesTooltip.java b/src/main/java/de/hysky/skyblocker/skyblock/item/tooltip/adders/MotesTooltip.java index 18d50fe825..7e90300d05 100644 --- a/src/main/java/de/hysky/skyblocker/skyblock/item/tooltip/adders/MotesTooltip.java +++ b/src/main/java/de/hysky/skyblocker/skyblock/item/tooltip/adders/MotesTooltip.java @@ -22,9 +22,10 @@ public MotesTooltip(int priority) { public void addToTooltip(@Nullable Slot focusedSlot, ItemStack stack, List lines) { final String internalID = stack.getSkyblockId(); if (TooltipInfoType.MOTES.hasOrNullWarning(internalID)) { - lines.add(Text.literal(String.format("%-20s", "Motes Price:")) + //noinspection DataFlowIssue --- The existence of the data is already checked via hasOrNullWarning, so the data is guaranteed to be present + lines.add(Text.literal("Motes Price:") .formatted(Formatting.LIGHT_PURPLE) - .append(getMotesMessage(TooltipInfoType.MOTES.getData().getInt(internalID), stack.getCount()))); + .align(getMotesMessage(TooltipInfoType.MOTES.getData().getInt(internalID), stack.getCount()), 100)); } } @@ -33,7 +34,7 @@ public boolean isEnabled() { return TooltipInfoType.MOTES.isTooltipEnabled(); } - private static Text getMotesMessage(int price, int count) { + private static MutableText getMotesMessage(int price, int count) { float motesMultiplier = SkyblockerConfigManager.get().otherLocations.rift.mcGrubberStacks * 0.05f + 1; // Calculate the total price diff --git a/src/main/java/de/hysky/skyblocker/skyblock/item/tooltip/adders/MuseumTooltip.java b/src/main/java/de/hysky/skyblocker/skyblock/item/tooltip/adders/MuseumTooltip.java index c4bf96c7d2..c8fa7bf555 100644 --- a/src/main/java/de/hysky/skyblocker/skyblock/item/tooltip/adders/MuseumTooltip.java +++ b/src/main/java/de/hysky/skyblocker/skyblock/item/tooltip/adders/MuseumTooltip.java @@ -27,16 +27,12 @@ public boolean isEnabled() { public void addToTooltip(@Nullable Slot focusedSlot, ItemStack stack, List lines) { final String internalID = stack.getSkyblockId(); if (TooltipInfoType.MUSEUM.hasOrNullWarning(internalID)) { + //noinspection DataFlowIssue --- The existence of the data is already checked via hasOrNullWarning, so the data is guaranteed to be present String itemCategory = TooltipInfoType.MUSEUM.getData().get(internalID); - String format = switch (itemCategory) { - case "Weapons" -> "%-18s"; - case "Armor" -> "%-19s"; - default -> "%-20s"; - }; //Special case the special category so that it doesn't always display not donated if (itemCategory.equals("Special")) { - lines.add(Text.literal(String.format(format, "Museum: (" + itemCategory + ")")) + lines.add(Text.literal("Museum: (" + itemCategory + ")") .formatted(Formatting.LIGHT_PURPLE)); } else { NbtCompound customData = ItemUtils.getCustomData(stack); @@ -44,10 +40,12 @@ public void addToTooltip(@Nullable Slot focusedSlot, ItemStack stack, List Formatting donatedIndicatorFormatting = isInMuseum ? Formatting.GREEN : Formatting.RED; - lines.add(Text.literal(String.format(format, "Museum (" + itemCategory + "):")) + lines.add(Text.literal("Museum (" + itemCategory + "):") .formatted(Formatting.LIGHT_PURPLE) - .append(Text.literal(isInMuseum ? "✔" : "✖").formatted(donatedIndicatorFormatting, Formatting.BOLD)) - .append(Text.literal(isInMuseum ? " Donated" : " Not Donated").formatted(donatedIndicatorFormatting))); + .align(Text.empty() + .append(Text.literal(isInMuseum ? "✔" : "✖").formatted(donatedIndicatorFormatting, Formatting.BOLD)) + .append(Text.literal(isInMuseum ? " Donated" : " Not Donated").formatted(donatedIndicatorFormatting)), + 100)); } } } diff --git a/src/main/java/de/hysky/skyblocker/skyblock/item/tooltip/adders/NpcPriceTooltip.java b/src/main/java/de/hysky/skyblocker/skyblock/item/tooltip/adders/NpcPriceTooltip.java index 6bb1bcdb8d..f44b28ebf4 100644 --- a/src/main/java/de/hysky/skyblocker/skyblock/item/tooltip/adders/NpcPriceTooltip.java +++ b/src/main/java/de/hysky/skyblocker/skyblock/item/tooltip/adders/NpcPriceTooltip.java @@ -36,9 +36,10 @@ public void addToTooltip(@Nullable Slot focusedSlot, ItemStack stack, List } else { amount = stack.getCount(); } - lines.add(Text.literal(String.format("%-21s", "NPC Sell Price:")) + //noinspection DataFlowIssue --- The data's presence is checked above, so it's safe to assume it's present here + lines.add(Text.literal("NPC Sell Price:") .formatted(Formatting.YELLOW) - .append(ItemTooltip.getCoinsMessage(TooltipInfoType.NPC.getData().getDouble(internalID), amount))); + .align(ItemTooltip.getCoinsMessage(TooltipInfoType.NPC.getData().getDouble(internalID), amount), 100)); } } } diff --git a/src/main/java/de/hysky/skyblocker/skyblock/item/tooltip/adders/ObtainedDateTooltip.java b/src/main/java/de/hysky/skyblocker/skyblock/item/tooltip/adders/ObtainedDateTooltip.java index 2071547cb9..6e015393fc 100644 --- a/src/main/java/de/hysky/skyblocker/skyblock/item/tooltip/adders/ObtainedDateTooltip.java +++ b/src/main/java/de/hysky/skyblocker/skyblock/item/tooltip/adders/ObtainedDateTooltip.java @@ -35,9 +35,8 @@ public boolean isEnabled() { public void addToTooltip(@Nullable Slot focusedSlot, ItemStack stack, List lines) { String timestamp = getTimestamp(stack); if (!timestamp.isEmpty()) { - lines.add(Text.empty() - .append(Text.literal(String.format("%-21s", "Obtained: ")).formatted(Formatting.LIGHT_PURPLE)) - .append(Text.literal(timestamp).formatted(Formatting.RED))); + lines.add(Text.literal("Obtained:").formatted(Formatting.LIGHT_PURPLE) + .align(Text.literal(timestamp).formatted(Formatting.RED), 100)); } } diff --git a/src/main/java/de/hysky/skyblocker/utils/render/gui/AlignedOrderedText.java b/src/main/java/de/hysky/skyblocker/utils/render/gui/AlignedOrderedText.java new file mode 100644 index 0000000000..ca17cb8c9b --- /dev/null +++ b/src/main/java/de/hysky/skyblocker/utils/render/gui/AlignedOrderedText.java @@ -0,0 +1,26 @@ +package de.hysky.skyblocker.utils.render.gui; + +import net.minecraft.client.font.TextRenderer; +import net.minecraft.text.CharacterVisitor; +import net.minecraft.text.OrderedText; + +import java.util.List; + +public record AlignedOrderedText(List segments) implements OrderedText { + @Override + public boolean accept(CharacterVisitor visitor) { + if (!(visitor instanceof TextRenderer.Drawer drawer)) return true; + float initialX = drawer.x; + float lastX = 0; + for (Segment segment : segments) { + float xOffset = lastX > segment.xOffset ? lastX : segment.xOffset; + drawer.x = initialX + xOffset; + boolean accepted = segment.text.accept(visitor); + if (!accepted) return false; + lastX = drawer.x - initialX; + } + return true; + } + + public record Segment(OrderedText text, int xOffset) {} +} diff --git a/src/main/resources/fabric.mod.json b/src/main/resources/fabric.mod.json index f9311b13d5..5f940facac 100644 --- a/src/main/resources/fabric.mod.json +++ b/src/main/resources/fabric.mod.json @@ -64,6 +64,9 @@ "loom:injected_interfaces": { "net/minecraft/class_1799": [ "de/hysky/skyblocker/injected/SkyblockerStack" + ], + "net/minecraft/class_5250": [ + "de/hysky/skyblocker/injected/AlignedText" ] } } diff --git a/src/main/resources/skyblocker.accesswidener b/src/main/resources/skyblocker.accesswidener index ba86af126f..99f3243169 100644 --- a/src/main/resources/skyblocker.accesswidener +++ b/src/main/resources/skyblocker.accesswidener @@ -20,3 +20,5 @@ extendable method net/minecraft/client/gui/screen/recipebook/RecipeBookWidget re extendable method net/minecraft/client/gui/screen/recipebook/RecipeBookWidget refreshTabButtons (Z)V extendable method net/minecraft/client/gui/screen/recipebook/RecipeBookWidget refreshSearchResults ()V extendable method net/minecraft/client/gui/screen/recipebook/RecipeBookWidget triggerPirateSpeakEasterEgg (Ljava/lang/String;)V +accessible class net/minecraft/client/font/TextRenderer$Drawer +accessible field net/minecraft/client/font/TextRenderer$Drawer x F diff --git a/src/main/resources/skyblocker.mixins.json b/src/main/resources/skyblocker.mixins.json index b011be6d59..7b87535674 100644 --- a/src/main/resources/skyblocker.mixins.json +++ b/src/main/resources/skyblocker.mixins.json @@ -28,6 +28,7 @@ "LeverBlockMixin", "MinecraftClientMixin", "MouseMixin", + "MutableTextMixin", "PingMeasurerMixin", "PlayerInventoryMixin", "PlayerListHudMixin", @@ -37,6 +38,7 @@ "ScoreboardMixin", "SignEditScreenMixin", "SocialInteractionsPlayerListWidgetMixin", + "TextHandlerMixin", "WindowMixin", "WorldRendererMixin", "YggdrasilMinecraftSessionServiceMixin", @@ -55,7 +57,9 @@ "accessors.SlotAccessor", "accessors.WorldRendererAccessor", "discordipc.ConnectionMixin", - "jgit.SystemReaderMixin" + "jgit.SystemReaderMixin", + "rei.ScreenOverlayImplFabricMixin", + "jei.RenderHelperMixin" ], "injectors": { "defaultRequire": 1