aboutsummaryrefslogtreecommitdiff
path: root/src/main/java
diff options
context:
space:
mode:
authorAnthony Hilyard <anthony.hilyard@gmail.com>2022-01-05 16:34:14 -0800
committerAnthony Hilyard <anthony.hilyard@gmail.com>2022-01-05 16:34:14 -0800
commitf8c05d3ed6f96393f34f4ab661e99d3083f893b6 (patch)
tree697a0b3719c439513ed03a3260b09bf3168df262 /src/main/java
parent2c50ee296e0daf85e6a087865b1d04bcebcbf565 (diff)
downloadIceberg-f8c05d3ed6f96393f34f4ab661e99d3083f893b6.tar.gz
Iceberg-f8c05d3ed6f96393f34f4ab661e99d3083f893b6.tar.bz2
Iceberg-f8c05d3ed6f96393f34f4ab661e99d3083f893b6.zip
Ported changes through 1.0.34. Fixed a crash issue for client-side dependants run on dedicated server. Cleaned up warnings in log file. Fixed incorrect background alpha bug.
Diffstat (limited to 'src/main/java')
-rw-r--r--src/main/java/com/anthonyhilyard/iceberg/events/RenderTooltipEvents.java52
-rw-r--r--src/main/java/com/anthonyhilyard/iceberg/mixin/ScreenMixin.java47
-rw-r--r--src/main/java/com/anthonyhilyard/iceberg/mixin/TextColorMixin.java36
-rw-r--r--src/main/java/com/anthonyhilyard/iceberg/util/ItemColor.java50
-rw-r--r--src/main/java/com/anthonyhilyard/iceberg/util/Selectors.java98
-rw-r--r--src/main/java/com/anthonyhilyard/iceberg/util/Tooltips.java167
6 files changed, 387 insertions, 63 deletions
diff --git a/src/main/java/com/anthonyhilyard/iceberg/events/RenderTooltipEvents.java b/src/main/java/com/anthonyhilyard/iceberg/events/RenderTooltipEvents.java
index d0693e3..622d3e0 100644
--- a/src/main/java/com/anthonyhilyard/iceberg/events/RenderTooltipEvents.java
+++ b/src/main/java/com/anthonyhilyard/iceberg/events/RenderTooltipEvents.java
@@ -3,24 +3,42 @@ package com.anthonyhilyard.iceberg.events;
import java.util.List;
import com.mojang.blaze3d.vertex.PoseStack;
+import com.mojang.datafixers.util.Either;
import net.fabricmc.fabric.api.event.Event;
import net.fabricmc.fabric.api.event.EventFactory;
import net.minecraft.client.gui.Font;
import net.minecraft.client.gui.screens.inventory.tooltip.ClientTooltipComponent;
+import net.minecraft.network.chat.FormattedText;
import net.minecraft.world.InteractionResult;
+import net.minecraft.world.inventory.tooltip.TooltipComponent;
import net.minecraft.world.item.ItemStack;
public final class RenderTooltipEvents
{
public RenderTooltipEvents() { }
+ public static final Event<RenderTooltipEvents.Gather> GATHER = EventFactory.createArrayBacked(RenderTooltipEvents.Gather.class,
+ callbacks -> (itemStack, screenWidth, screenHeight, tooltipElements, maxWidth, index) -> {
+ GatherResult result = new GatherResult(InteractionResult.PASS, maxWidth, tooltipElements);
+ for (RenderTooltipEvents.Gather callback : callbacks)
+ {
+ result = callback.onGather(itemStack, screenWidth, screenHeight, tooltipElements, maxWidth, index);
+
+ if (result.result != InteractionResult.PASS)
+ {
+ return result;
+ }
+ }
+ return result;
+ });
+
public static final Event<RenderTooltipEvents.PreExt> PREEXT = EventFactory.createArrayBacked(RenderTooltipEvents.PreExt.class,
- callbacks -> (stack, components, poseStack, x, y, screenWidth, screenHeight, font, comparison) -> {
+ callbacks -> (stack, components, poseStack, x, y, screenWidth, screenHeight, font, comparison, index) -> {
PreExtResult result = new PreExtResult(InteractionResult.PASS, x, y, screenWidth, screenHeight, font);
for (RenderTooltipEvents.PreExt callback : callbacks)
{
- result = callback.onPre(stack, components, poseStack, x, y, screenWidth, screenHeight, font, comparison);
+ result = callback.onPre(stack, components, poseStack, x, y, screenWidth, screenHeight, font, comparison, index);
if (result.result != InteractionResult.PASS)
{
@@ -46,11 +64,11 @@ public final class RenderTooltipEvents
});
public static final Event<RenderTooltipEvents.ColorExt> COLOREXT = EventFactory.createArrayBacked(RenderTooltipEvents.ColorExt.class,
- callbacks -> (stack, components, poseStack, x, y, font, backgroundStart, backgroundEnd, borderStart, borderEnd, comparison) -> {
+ callbacks -> (stack, components, poseStack, x, y, font, backgroundStart, backgroundEnd, borderStart, borderEnd, comparison, index) -> {
ColorExtResult result = new ColorExtResult(backgroundStart, backgroundEnd, borderStart, borderEnd);
for (RenderTooltipEvents.ColorExt callback : callbacks)
{
- result = callback.onColor(stack, components, poseStack, x, y, font, result.backgroundStart, result.backgroundEnd, result.borderStart, result.borderEnd, comparison);
+ result = callback.onColor(stack, components, poseStack, x, y, font, result.backgroundStart, result.backgroundEnd, result.borderStart, result.borderEnd, comparison, index);
}
return result;
});
@@ -66,6 +84,14 @@ public final class RenderTooltipEvents
return result;
});
+ public static final Event<RenderTooltipEvents.PostExt> POSTEXT = EventFactory.createArrayBacked(RenderTooltipEvents.PostExt.class,
+ callbacks -> (stack, components, poseStack, x, y, font, width, height, comparison, index) -> {
+ for (RenderTooltipEvents.PostExt callback : callbacks)
+ {
+ callback.onPost(stack, components, poseStack, x, y, font, width, height, comparison, index);
+ }
+ });
+
public static final Event<RenderTooltipEvents.Post> POST = EventFactory.createArrayBacked(RenderTooltipEvents.Post.class,
callbacks -> (stack, components, poseStack, x, y, font, width, height, comparison) -> {
for (RenderTooltipEvents.Post callback : callbacks)
@@ -75,9 +101,15 @@ public final class RenderTooltipEvents
});
@FunctionalInterface
+ public interface Gather
+ {
+ GatherResult onGather(ItemStack itemStack, int screenWidth, int screenHeight, List<Either<FormattedText, TooltipComponent>> tooltipElements, int maxWidth, int index);
+ }
+
+ @FunctionalInterface
public interface PreExt
{
- PreExtResult onPre(ItemStack stack, List<ClientTooltipComponent> components, PoseStack poseStack, int x, int y, int screenWidth, int screenHeight, Font font, boolean comparison);
+ PreExtResult onPre(ItemStack stack, List<ClientTooltipComponent> components, PoseStack poseStack, int x, int y, int screenWidth, int screenHeight, Font font, boolean comparison, int index);
}
@Deprecated
@@ -90,7 +122,7 @@ public final class RenderTooltipEvents
@FunctionalInterface
public interface ColorExt
{
- ColorExtResult onColor(ItemStack stack, List<ClientTooltipComponent> components, PoseStack poseStack, int x, int y, Font font, int backgroundStart, int backgroundEnd, int borderStart, int borderEnd, boolean comparison);
+ ColorExtResult onColor(ItemStack stack, List<ClientTooltipComponent> components, PoseStack poseStack, int x, int y, Font font, int backgroundStart, int backgroundEnd, int borderStart, int borderEnd, boolean comparison, int index);
}
@Deprecated
@@ -101,11 +133,19 @@ public final class RenderTooltipEvents
}
@FunctionalInterface
+ public interface PostExt
+ {
+ void onPost(ItemStack stack, List<ClientTooltipComponent> components, PoseStack poseStack, int x, int y, Font font, int width, int height, boolean comparison, int index);
+ }
+
+ @Deprecated
+ @FunctionalInterface
public interface Post
{
void onPost(ItemStack stack, List<ClientTooltipComponent> components, PoseStack poseStack, int x, int y, Font font, int width, int height, boolean comparison);
}
+ public record GatherResult(InteractionResult result, int maxWidth, List<Either<FormattedText, TooltipComponent>> tooltipElements) {}
public record PreExtResult(InteractionResult result, int x, int y, int screenWidth, int screenHeight, Font font) {}
public record ColorExtResult(int backgroundStart, int backgroundEnd, int borderStart, int borderEnd) {}
public record ColorResult(int background, int borderStart, int borderEnd) {}
diff --git a/src/main/java/com/anthonyhilyard/iceberg/mixin/ScreenMixin.java b/src/main/java/com/anthonyhilyard/iceberg/mixin/ScreenMixin.java
index 55a97c3..a797c9f 100644
--- a/src/main/java/com/anthonyhilyard/iceberg/mixin/ScreenMixin.java
+++ b/src/main/java/com/anthonyhilyard/iceberg/mixin/ScreenMixin.java
@@ -1,10 +1,13 @@
package com.anthonyhilyard.iceberg.mixin;
import java.util.List;
+import java.util.Optional;
import com.anthonyhilyard.iceberg.events.RenderTooltipEvents;
import com.anthonyhilyard.iceberg.events.RenderTooltipEvents.ColorExtResult;
import com.anthonyhilyard.iceberg.events.RenderTooltipEvents.ColorResult;
+import com.anthonyhilyard.iceberg.events.RenderTooltipEvents.PreExtResult;
+import com.anthonyhilyard.iceberg.util.Tooltips;
import com.google.common.collect.Lists;
import com.mojang.blaze3d.vertex.BufferBuilder;
import com.mojang.blaze3d.vertex.PoseStack;
@@ -26,9 +29,11 @@ import net.minecraft.client.gui.components.events.GuiEventListener;
import net.minecraft.client.gui.screens.Screen;
import net.minecraft.client.gui.screens.inventory.AbstractContainerScreen;
import net.minecraft.client.gui.screens.inventory.tooltip.ClientTooltipComponent;
+import net.minecraft.network.chat.Component;
import net.minecraft.world.InteractionResult;
import net.minecraft.world.inventory.AbstractContainerMenu;
import net.minecraft.world.inventory.Slot;
+import net.minecraft.world.inventory.tooltip.TooltipComponent;
import net.minecraft.world.item.ItemStack;
@Mixin(Screen.class)
@@ -40,10 +45,40 @@ public class ScreenMixin extends AbstractContainerEventHandler
@Shadow
private List<GuiEventListener> children = Lists.newArrayList();
+ private ItemStack tooltipStack = ItemStack.EMPTY;
+
+ @Inject(method = "renderTooltip(Lcom/mojang/blaze3d/vertex/PoseStack;Lnet/minecraft/world/item/ItemStack;II)V", at = @At(value = "HEAD"))
+ protected void renderTooltipHead(PoseStack poseStack, ItemStack itemStack, int x, int y, CallbackInfo info)
+ {
+ tooltipStack = itemStack;
+ }
+
+ @Inject(method = "renderTooltip(Lcom/mojang/blaze3d/vertex/PoseStack;Lnet/minecraft/world/item/ItemStack;II)V", at = @At(value = "TAIL"))
+ protected void renderTooltipTail(PoseStack poseStack, ItemStack itemStack, int x, int y, CallbackInfo info)
+ {
+ tooltipStack = ItemStack.EMPTY;
+ }
+
+ @Inject(method = "renderTooltip(Lcom/mojang/blaze3d/vertex/PoseStack;Ljava/util/List;Ljava/util/Optional;II)V",
+ at = @At(value = "INVOKE", target = "Lnet/minecraft/client/gui/screens/Screen;renderTooltipInternal(Lcom/mojang/blaze3d/vertex/PoseStack;Ljava/util/List;II)V"),
+ locals = LocalCapture.CAPTURE_FAILEXCEPTION)
+ public void renderTooltip(PoseStack poseStack, List<Component> textComponents, Optional<TooltipComponent> itemComponent, int x, int y, CallbackInfo info, List<ClientTooltipComponent> components)
+ {
+ Screen self = (Screen)(Object)this;
+
+ List<ClientTooltipComponent> newComponents = Tooltips.gatherTooltipComponents(tooltipStack, textComponents, itemComponent, x, self.width, self.height, null, font, -1);
+ if (newComponents != null && !newComponents.isEmpty())
+ {
+ components.clear();
+ components.addAll(newComponents);
+ }
+ }
+
@SuppressWarnings({"unchecked", "deprecation"})
@Inject(method = "renderTooltipInternal", at = @At(value = "HEAD"), cancellable = true)
private void preRenderTooltipInternal(PoseStack poseStack, List<ClientTooltipComponent> components, int x, int y, CallbackInfo info)
{
+ PreExtResult eventResult = null;
Screen self = (Screen)(Object)this;
if (self instanceof AbstractContainerScreen)
{
@@ -53,7 +88,9 @@ public class ScreenMixin extends AbstractContainerEventHandler
if (hoveredSlot != null)
{
ItemStack tooltipStack = hoveredSlot.getItem();
- InteractionResult result = RenderTooltipEvents.PREEXT.invoker().onPre(tooltipStack, components, poseStack, x, y, self.width, self.height, font, false).result();
+ InteractionResult result = InteractionResult.PASS;
+ eventResult = RenderTooltipEvents.PREEXT.invoker().onPre(tooltipStack, components, poseStack, x, y, self.width, self.height, font, false, 0);
+ result = eventResult.result();
if (result != InteractionResult.PASS)
{
@@ -100,7 +137,7 @@ public class ScreenMixin extends AbstractContainerEventHandler
@SuppressWarnings({"unchecked", "deprecation"})
@Inject(method = "renderTooltipInternal", at = @At(value = "INVOKE",
- target = "Lnet/minecraft/client/gui/screens/Screen;fillGradient(Lcom/mojang/math/Matrix4f;Lcom/mojang/blaze3d/vertex/BufferBuilder;IIIIIII)V", shift = Shift.BEFORE),
+ target = "Lnet/minecraft/client/gui/screens/Screen;fillGradient(Lcom/mojang/math/Matrix4f;Lcom/mojang/blaze3d/vertex/BufferBuilder;IIIIIII)V", ordinal = 0, shift = Shift.BEFORE),
locals = LocalCapture.CAPTURE_FAILEXCEPTION)
private void preFillGradient(PoseStack poseStack, List<ClientTooltipComponent> components, int x, int y, CallbackInfo info,
int __, int ___, int left, int top, int width, int height, int background, int borderStart, int borderEnd,
@@ -123,7 +160,7 @@ public class ScreenMixin extends AbstractContainerEventHandler
int backgroundEnd = background;
// Do colors now, sure why not.
- ColorExtResult result = RenderTooltipEvents.COLOREXT.invoker().onColor(tooltipStack, components, poseStack, x, y, font, background, background, borderStart, borderEnd, false);
+ ColorExtResult result = RenderTooltipEvents.COLOREXT.invoker().onColor(tooltipStack, components, poseStack, x, y, font, background, backgroundEnd, borderStart, borderEnd, false, 0);
if (result != null)
{
background = result.backgroundStart();
@@ -141,7 +178,6 @@ public class ScreenMixin extends AbstractContainerEventHandler
borderEnd = colorResult.borderEnd();
}
-
Screen.fillGradient(matrix4f, bufferBuilder, left - 3, top - 4, left + width + 3, top - 3, zIndex, background, background);
Screen.fillGradient(matrix4f, bufferBuilder, left - 3, top + height + 3, left + width + 3, top + height + 4, zIndex, backgroundEnd, backgroundEnd);
Screen.fillGradient(matrix4f, bufferBuilder, left - 3, top - 3, left + width + 3, top + height + 3, zIndex, background, backgroundEnd);
@@ -154,7 +190,7 @@ public class ScreenMixin extends AbstractContainerEventHandler
}
}
- @SuppressWarnings("unchecked")
+ @SuppressWarnings({"unchecked", "deprecation"})
@Inject(method = "renderTooltipInternal", at = @At(value = "TAIL"), locals = LocalCapture.CAPTURE_FAILEXCEPTION)
private void renderTooltipInternal(PoseStack poseStack, List<ClientTooltipComponent> components, int x, int y, CallbackInfo info, int tooltipWidth, int tooltipHeight, int postX, int postY)
{
@@ -166,6 +202,7 @@ public class ScreenMixin extends AbstractContainerEventHandler
if (hoveredSlot != null)
{
ItemStack tooltipStack = hoveredSlot.getItem();
+ RenderTooltipEvents.POSTEXT.invoker().onPost(tooltipStack, components, poseStack, postX, postY, font, tooltipWidth, tooltipHeight, false, 0);
RenderTooltipEvents.POST.invoker().onPost(tooltipStack, components, poseStack, postX, postY, font, tooltipWidth, tooltipHeight, false);
}
}
diff --git a/src/main/java/com/anthonyhilyard/iceberg/mixin/TextColorMixin.java b/src/main/java/com/anthonyhilyard/iceberg/mixin/TextColorMixin.java
new file mode 100644
index 0000000..02bca31
--- /dev/null
+++ b/src/main/java/com/anthonyhilyard/iceberg/mixin/TextColorMixin.java
@@ -0,0 +1,36 @@
+package com.anthonyhilyard.iceberg.mixin;
+
+import org.spongepowered.asm.mixin.Mixin;
+import org.spongepowered.asm.mixin.injection.At;
+import org.spongepowered.asm.mixin.injection.Inject;
+import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
+
+import net.minecraft.network.chat.TextColor;
+
+@Mixin(TextColor.class)
+public class TextColorMixin
+{
+ /**
+ * Fix an issue in TextColor parsing that makes it so only alpha values up to 0x7F are supported.
+ */
+ @Inject(method = "parseColor", at = @At("HEAD"), cancellable = true)
+ private static boolean parseColor(String colorString, CallbackInfoReturnable<TextColor> info)
+ {
+ if (!colorString.startsWith("#"))
+ {
+ return false;
+ }
+
+ try
+ {
+ int i = Integer.parseUnsignedInt(colorString.substring(1), 16);
+ info.setReturnValue(TextColor.fromRgb(i));
+ return true;
+ }
+ catch (NumberFormatException numberformatexception)
+ {
+ info.setReturnValue(null);
+ return true;
+ }
+ }
+} \ No newline at end of file
diff --git a/src/main/java/com/anthonyhilyard/iceberg/util/ItemColor.java b/src/main/java/com/anthonyhilyard/iceberg/util/ItemColor.java
index 6f8de4e..74e4772 100644
--- a/src/main/java/com/anthonyhilyard/iceberg/util/ItemColor.java
+++ b/src/main/java/com/anthonyhilyard/iceberg/util/ItemColor.java
@@ -1,12 +1,36 @@
package com.anthonyhilyard.iceberg.util;
import net.minecraft.world.item.ItemStack;
+import net.minecraft.world.item.TooltipFlag;
import net.minecraft.network.chat.TextColor;
+import net.minecraft.util.FormattedCharSink;
import net.minecraft.network.chat.Component;
+import net.minecraft.network.chat.Style;
+
+import java.util.List;
+
import net.minecraft.ChatFormatting;
public class ItemColor
{
+ private static class ColorCollector implements FormattedCharSink
+ {
+ private TextColor color = null;
+
+ @Override
+ public boolean accept(int index, Style style, int codePoint)
+ {
+ if (style.getColor() != null)
+ {
+ color = style.getColor();
+ return false;
+ }
+ return true;
+ }
+
+ public TextColor getColor() { return color; }
+ }
+
public static TextColor findFirstColorCode(Component textComponent)
{
// This function finds the first specified color code in the given text component.
@@ -20,7 +44,7 @@ public class ItemColor
try
{
ChatFormatting format = ChatFormatting.getByCode(rawTitle.charAt(i + 1));
- if (format.isColor())
+ if (format != null && format.isColor())
{
return TextColor.fromLegacyFormat(format);
}
@@ -36,6 +60,7 @@ public class ItemColor
return null;
}
}
+
return null;
}
@@ -61,13 +86,32 @@ public class ItemColor
result = item.getHoverName().getStyle().getColor();
}
- // Finally if there is a color code specified for the item name, use that.
- TextColor formattingColor = findFirstColorCode(item.getItem().getName(item));
+ // If there is a color code specified for the item name, use that.
+ TextColor formattingColor = findFirstColorCode(item.getHoverName());
if (formattingColor != null)
{
result = formattingColor;
}
+ // Finally, if there is a color style stored per-character, use the first one found.
+ ColorCollector colorCollector = new ColorCollector();
+ item.getHoverName().getVisualOrderText().accept(colorCollector);
+ if (colorCollector.getColor() != null)
+ {
+ result = colorCollector.getColor();
+ }
+
+ // If we haven't found a color or we're still using the rarity color, check the actual tooltip.
+ // This is slow, so it better get cached externally!
+ if (result == null || result.equals(item.getDisplayName().getStyle().getColor()))
+ {
+ List<Component> lines = item.getTooltipLines(null, TooltipFlag.Default.ADVANCED);
+ if (!lines.isEmpty())
+ {
+ result = lines.get(0).getStyle().getColor();
+ }
+ }
+
// Fallback to the default TextColor if we somehow haven't found a single valid TextColor.
if (result == null)
{
diff --git a/src/main/java/com/anthonyhilyard/iceberg/util/Selectors.java b/src/main/java/com/anthonyhilyard/iceberg/util/Selectors.java
index af5c3af..c097323 100644
--- a/src/main/java/com/anthonyhilyard/iceberg/util/Selectors.java
+++ b/src/main/java/com/anthonyhilyard/iceberg/util/Selectors.java
@@ -3,15 +3,16 @@ package com.anthonyhilyard.iceberg.util;
import net.minecraft.core.Registry;
import net.minecraft.world.item.ItemStack;
+import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;
import java.util.function.BiPredicate;
import java.util.List;
-import net.minecraft.client.Minecraft;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.nbt.NumericTag;
import net.minecraft.nbt.Tag;
+import net.minecraft.nbt.ListTag;
import net.minecraft.network.chat.Component;
import net.minecraft.network.chat.TextColor;
import net.minecraft.resources.ResourceLocation;
@@ -72,6 +73,25 @@ public class Selectors
});
}};
+ public static record SelectorDocumentation(String name, String description, List<String> examples)
+ {
+ public SelectorDocumentation(String name, String description, String... examples) { this(name, description, Arrays.asList(examples)); }
+ }
+
+ public static List<SelectorDocumentation> selectorDocumentation()
+ {
+ return Arrays.asList(
+ new SelectorDocumentation("Item name", "Use item name for vanilla items or include mod name for modded items.", "minecraft:stick", "iron_ore"),
+ new SelectorDocumentation("Tag", "$ followed by tag name.", "$forge:stone", "$planks"),
+ new SelectorDocumentation("Mod name", "@ followed by mod identifier.", "@spoiledeggs"),
+ new SelectorDocumentation("Rarity", "! followed by item's rarity. This is ONLY vanilla rarities.", "!uncommon", "!rare", "!epic"),
+ new SelectorDocumentation("Item name color", "# followed by color hex code, the hex code must match exactly.", "#23F632"),
+ new SelectorDocumentation("Display name", "% followed by any text. Will match any item with this text in its tooltip display name.", "%Netherite", "%[Uncommon]"),
+ new SelectorDocumentation("Tooltip text", "Will match any item with this text anywhere in the tooltip text (besides the name).", "^Legendary"),
+ new SelectorDocumentation("NBT tag", "& followed by tag name and optional comparator (=, >, <, or !=) and value, in the format <tag><comparator><value> or just <tag>.", "&Damage=0", "&Tier>1", "&map!=128", "&Enchantments")
+ );
+ }
+
/**
* Returns true if this selector is syntactically valid.
* @param value The selector.
@@ -174,8 +194,7 @@ public class Selectors
// Tooltip text
else if (selector.startsWith("^"))
{
- Minecraft mc = Minecraft.getInstance();
- List<Component> lines = item.getTooltipLines(mc.player, TooltipFlag.Default.ADVANCED);
+ List<Component> lines = item.getTooltipLines(null, TooltipFlag.Default.ADVANCED);
String tooltipText = "";
// Skip title line.
@@ -212,57 +231,66 @@ public class Selectors
}
}
- // Look for a tag matching the given name.
- Tag matchedTag = getSubtag(item.getTag(), tagName);
- if (matchedTag != null)
- {
- // A tag value of null means that we are just looking for the presence of this tag.
- if (tagValue == null)
- {
- return true;
- }
- // Otherwise, we will use the provided comparator.
- else
- {
- if (valueChecker != null)
- {
- return valueChecker.test(matchedTag, tagValue);
- }
- }
- }
+ // Look for a tag matching the given name and value.
+ return findMatchingSubtag(item.getTag(), tagName, tagValue, valueChecker);
}
return false;
}
- /**
- * Retrieves the first inner tag with the given key, or null if there is no match.
- */
- private static Tag getSubtag(CompoundTag tag, String key)
+ private static boolean findMatchingSubtag(Tag tag, String key, String value, BiPredicate<Tag, String> valueChecker)
{
if (tag == null)
{
- return null;
+ return false;
}
- if (tag.contains(key))
+ if (tag.getId() == Tag.TAG_COMPOUND)
{
- return tag.get(key);
+ CompoundTag compoundTag = (CompoundTag)tag;
+
+ if (compoundTag.contains(key))
+ {
+ // Just checking presence.
+ if (value == null && valueChecker == null)
+ {
+ return true;
+ }
+ // Otherwise, we will use the provided comparator.
+ else
+ {
+ return valueChecker.test(compoundTag.get(key), value);
+ }
+ }
+ else
+ {
+ for (String innerKey : compoundTag.getAllKeys())
+ {
+ if (compoundTag.getTagType(innerKey) == Tag.TAG_LIST || compoundTag.getTagType(innerKey) == Tag.TAG_COMPOUND)
+ {
+ if (findMatchingSubtag(compoundTag.get(innerKey), key, value, valueChecker))
+ {
+ return true;
+ }
+ }
+ }
+ return false;
+ }
}
- else
+ else if (tag.getId() == Tag.TAG_LIST)
{
- for (String innerKey : tag.getAllKeys())
+ ListTag listTag = (ListTag)tag;
+ for (Tag innerTag : listTag)
{
- if (tag.getTagType(innerKey) == Tag.TAG_COMPOUND)
+ if (innerTag.getId() == Tag.TAG_LIST || innerTag.getId() == Tag.TAG_COMPOUND)
{
- Tag innerTag = getSubtag(tag.getCompound(innerKey), key);
- if (innerTag != null)
+ if (findMatchingSubtag(innerTag, key, value, valueChecker))
{
- return innerTag;
+ return true;
}
}
}
- return null;
}
+ return false;
}
} \ No newline at end of file
diff --git a/src/main/java/com/anthonyhilyard/iceberg/util/Tooltips.java b/src/main/java/com/anthonyhilyard/iceberg/util/Tooltips.java
index 6d48c1a..7143b25 100644
--- a/src/main/java/com/anthonyhilyard/iceberg/util/Tooltips.java
+++ b/src/main/java/com/anthonyhilyard/iceberg/util/Tooltips.java
@@ -2,9 +2,14 @@ package com.anthonyhilyard.iceberg.util;
import java.util.ArrayList;
import java.util.List;
+import java.util.Optional;
+import java.util.function.Function;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
import com.anthonyhilyard.iceberg.events.RenderTooltipEvents;
import com.anthonyhilyard.iceberg.events.RenderTooltipEvents.ColorExtResult;
+import com.anthonyhilyard.iceberg.events.RenderTooltipEvents.GatherResult;
import com.anthonyhilyard.iceberg.events.RenderTooltipEvents.PreExtResult;
import com.mojang.blaze3d.vertex.PoseStack;
@@ -15,10 +20,17 @@ import net.minecraft.client.renderer.MultiBufferSource;
import net.minecraft.client.renderer.Rect2i;
import net.minecraft.client.renderer.MultiBufferSource.BufferSource;
import net.minecraft.client.renderer.entity.ItemRenderer;
+import net.minecraft.locale.Language;
+import net.minecraft.network.chat.Component;
+import net.minecraft.network.chat.FormattedText;
+import net.minecraft.network.chat.Style;
+import net.minecraft.util.FormattedCharSequence;
import com.mojang.blaze3d.vertex.Tesselator;
+import com.mojang.datafixers.util.Either;
import net.minecraft.world.InteractionResult;
+import net.minecraft.world.inventory.tooltip.TooltipComponent;
import net.minecraft.world.item.ItemStack;
import com.mojang.math.Matrix4f;
@@ -85,12 +97,13 @@ public class Tooltips
Rect2i rect, int screenWidth, int screenHeight,
int backgroundColor, int borderColorStart, int borderColorEnd, boolean comparison, boolean constrain)
{
- renderItemTooltip(stack, poseStack, info, rect, screenWidth, screenHeight, backgroundColor, backgroundColor, borderColorStart, borderColorEnd, comparison, constrain);
+ renderItemTooltip(stack, poseStack, info, rect, screenWidth, screenHeight, backgroundColor, backgroundColor, borderColorStart, borderColorEnd, comparison, constrain, false, 0);
}
public static void renderItemTooltip(final ItemStack stack, PoseStack poseStack, TooltipInfo info,
- Rect2i rect, int screenWidth, int screenHeight,
- int backgroundColorStart, int backgroundColorEnd, int borderColorStart, int borderColorEnd, boolean comparison, boolean constrain)
+ Rect2i rect, int screenWidth, int screenHeight,
+ int backgroundColorStart, int backgroundColorEnd, int borderColorStart, int borderColorEnd,
+ boolean comparison, boolean constrain, boolean centeredTitle, int index)
{
if (info.components.isEmpty())
{
@@ -103,10 +116,16 @@ public class Tooltips
init(Minecraft.getInstance());
}
- int rectX = rect.getX() - 8;
- int rectY = rect.getY() + 18;
+ // Center the title now if needed.
+ if (centeredTitle)
+ {
+ info = new TooltipInfo(centerTitle(info.getComponents(), info.getFont(), rect.getWidth()), info.getFont());
+ }
+
+ int rectX = rect.getX() + 4;
+ int rectY = rect.getY() + 4;
- PreExtResult preResult = RenderTooltipEvents.PREEXT.invoker().onPre(stack, info.getComponents(), poseStack, rectX, rectY, screenWidth, screenHeight, info.getFont(), comparison);
+ PreExtResult preResult = RenderTooltipEvents.PREEXT.invoker().onPre(stack, info.getComponents(), poseStack, rectX, rectY, screenWidth, screenHeight, info.getFont(), comparison, index);
if (preResult.result() != InteractionResult.PASS)
{
return;
@@ -126,7 +145,7 @@ public class Tooltips
itemRenderer.blitOffset = zLevel;
Matrix4f mat = poseStack.last().pose();
- ColorExtResult colors = RenderTooltipEvents.COLOREXT.invoker().onColor(stack, info.components, poseStack, rectX, rectY, info.getFont(), backgroundColorStart, backgroundColorEnd, borderColorStart, borderColorEnd, comparison);
+ ColorExtResult colors = RenderTooltipEvents.COLOREXT.invoker().onColor(stack, info.components, poseStack, rectX, rectY, info.getFont(), backgroundColorStart, backgroundColorEnd, borderColorStart, borderColorEnd, comparison, index);
backgroundColorStart = colors.backgroundStart();
backgroundColorEnd = colors.backgroundEnd();
@@ -168,11 +187,84 @@ public class Tooltips
itemRenderer.blitOffset = f;
- RenderTooltipEvents.POST.invoker().onPost(stack, info.getComponents(), poseStack, rectX, rectY, info.getFont(), rect.getWidth(), rect.getHeight(), comparison);
+ RenderTooltipEvents.POSTEXT.invoker().onPost(stack, info.getComponents(), poseStack, rectX, rectY, info.getFont(), rect.getWidth(), rect.getHeight(), comparison, index);
+ }
+
+ public static List<ClientTooltipComponent> gatherTooltipComponents(ItemStack stack, List<? extends FormattedText> textElements, Optional<TooltipComponent> itemComponent,
+ int mouseX, int screenWidth, int screenHeight, Font forcedFont, Font fallbackFont, int maxWidth)
+ {
+ return gatherTooltipComponents(stack, textElements, itemComponent, mouseX, screenWidth, screenHeight, forcedFont, fallbackFont, maxWidth, 0);
+ }
+
+ public static List<ClientTooltipComponent> gatherTooltipComponents(ItemStack stack, List<? extends FormattedText> textElements, Optional<TooltipComponent> itemComponent,
+ int mouseX, int screenWidth, int screenHeight, Font forcedFont, Font fallbackFont, int maxWidth, int index)
+ {
+ final Font font = forcedFont == null ? fallbackFont : forcedFont;
+
+ List<Either<FormattedText, TooltipComponent>> elements = textElements.stream()
+ .map((Function<FormattedText, Either<FormattedText, TooltipComponent>>) Either::left)
+ .collect(Collectors.toCollection(ArrayList::new));
+
+ itemComponent.ifPresent(c -> elements.add(1, Either.right(c)));
+
+ GatherResult eventResult = RenderTooltipEvents.GATHER.invoker().onGather(stack, screenWidth, screenHeight, elements, maxWidth, index);
+ if (eventResult.result() != InteractionResult.PASS)
+ {
+ return List.of();
+ }
+
+ // Wrap text as needed. First get the maximum width of all components.
+ int tooltipTextWidth = eventResult.tooltipElements().stream()
+ .mapToInt(either -> either.map(font::width, component -> 0))
+ .max().orElse(0);
+
+ boolean needsWrap = false;
+
+ int tooltipX = mouseX + 12;
+ if (tooltipX + tooltipTextWidth + 4 > screenWidth)
+ {
+ tooltipX = mouseX - 16 - tooltipTextWidth;
+ if (tooltipX < 4)
+ {
+ if (mouseX > screenWidth / 2)
+ {
+ tooltipTextWidth = mouseX - 12 - 8;
+ }
+ else
+ {
+ tooltipTextWidth = screenWidth - 16 - mouseX;
+ }
+ needsWrap = true;
+ }
+ }
+
+ if (eventResult.maxWidth() > 0 && tooltipTextWidth > eventResult.maxWidth())
+ {
+ tooltipTextWidth = eventResult.maxWidth();
+ needsWrap = true;
+ }
+
+ final int tooltipTextWidthFinal = tooltipTextWidth;
+ if (needsWrap)
+ {
+ return eventResult.tooltipElements().stream().flatMap(either -> either.map(text ->
+ font.split(text, tooltipTextWidthFinal).stream().map(ClientTooltipComponent::create),
+ component -> Stream.of(ClientTooltipComponent.create(component)))).toList();
+ }
+
+ return eventResult.tooltipElements().stream().map(either -> either.map(text ->
+ ClientTooltipComponent.create(text instanceof Component ? ((Component) text).getVisualOrderText() : Language.getInstance().getVisualOrder(text)),
+ ClientTooltipComponent::create)).toList();
}
- public static Rect2i calculateRect(final ItemStack stack, PoseStack poseStack, List<ClientTooltipComponent> components, int mouseX, int mouseY,
- int screenWidth, int screenHeight, int maxTextWidth, Font font)
+ public static Rect2i calculateRect(final ItemStack stack, PoseStack poseStack, List<ClientTooltipComponent> components,
+ int mouseX, int mouseY,int screenWidth, int screenHeight, int maxTextWidth, Font font)
+ {
+ return calculateRect(stack, poseStack, components, mouseX, mouseY, screenWidth, screenHeight, maxTextWidth, font, 0, false);
+ }
+
+ public static Rect2i calculateRect(final ItemStack stack, PoseStack poseStack, List<ClientTooltipComponent> components,
+ int mouseX, int mouseY,int screenWidth, int screenHeight, int maxTextWidth, Font font, int minWidth, boolean centeredTitle)
{
Rect2i rect = new Rect2i(0, 0, 0, 0);
if (components == null || components.isEmpty() || stack == null)
@@ -181,7 +273,7 @@ public class Tooltips
}
// Generate a tooltip event even though we aren't rendering anything in case event handlers are modifying the input values.
- PreExtResult preResult = RenderTooltipEvents.PREEXT.invoker().onPre(stack, components, poseStack, mouseX, mouseY, screenWidth, screenHeight, font, false);
+ PreExtResult preResult = RenderTooltipEvents.PREEXT.invoker().onPre(stack, components, poseStack, mouseX, mouseY, screenWidth, screenHeight, font, false, 0);
if (preResult.result() != InteractionResult.PASS)
{
return rect;
@@ -193,9 +285,14 @@ public class Tooltips
screenHeight = preResult.screenHeight();
font = preResult.font();
- int tooltipTextWidth = 0;
+ int tooltipTextWidth = minWidth;
int tooltipHeight = components.size() == 1 ? -2 : 0;
+ if (centeredTitle)
+ {
+ components = centerTitle(components, font, minWidth);
+ }
+
for (ClientTooltipComponent component : components)
{
int componentWidth = component.getWidth(font);
@@ -219,7 +316,49 @@ public class Tooltips
tooltipY = screenHeight - tooltipHeight - 6;
}
- rect = new Rect2i(tooltipX - 4, tooltipY - 4, tooltipTextWidth + 8, tooltipHeight + 8);
+ rect = new Rect2i(tooltipX - 2, tooltipY - 4, tooltipTextWidth, tooltipHeight);
return rect;
}
-}
+
+ public static List<ClientTooltipComponent> centerTitle(List<ClientTooltipComponent> components, Font font, int minWidth)
+ {
+ // Calculate tooltip width first.
+ int tooltipWidth = minWidth;
+
+ for (ClientTooltipComponent clienttooltipcomponent : components)
+ {
+ if (clienttooltipcomponent == null)
+ {
+ return components;
+ }
+ int componentWidth = clienttooltipcomponent.getWidth(font);
+ if (componentWidth > tooltipWidth)
+ {
+ tooltipWidth = componentWidth;
+ }
+ }
+
+ // TODO: If the title is multiple lines, we need to extend this for each one.
+
+ List<FormattedText> recomposedLines = StringRecomposer.recompose(List.of(components.get(0)));
+ if (recomposedLines.isEmpty())
+ {
+ return components;
+ }
+
+ List<ClientTooltipComponent> result = new ArrayList<>(components);
+
+ FormattedCharSequence title = Language.getInstance().getVisualOrder(recomposedLines.get(0));
+ FormattedCharSequence space = FormattedCharSequence.forward(" ", Style.EMPTY);
+ while (result.get(0).getWidth(font) < tooltipWidth)
+ {
+ title = FormattedCharSequence.fromList(List.of(space, title, space));
+ if (title == null)
+ {
+ break;
+ }
+ result.set(0, ClientTooltipComponent.create(title));
+ }
+ return result;
+ }
+} \ No newline at end of file