diff --git a/text-minimessage/src/main/java/net/kyori/adventure/text/minimessage/tag/standard/AbstractColorChangingTag.java b/text-minimessage/src/main/java/net/kyori/adventure/text/minimessage/tag/standard/AbstractAttributeChangingTag.java
similarity index 80%
rename from text-minimessage/src/main/java/net/kyori/adventure/text/minimessage/tag/standard/AbstractColorChangingTag.java
rename to text-minimessage/src/main/java/net/kyori/adventure/text/minimessage/tag/standard/AbstractAttributeChangingTag.java
index 2639981ac3..ad48736e93 100644
--- a/text-minimessage/src/main/java/net/kyori/adventure/text/minimessage/tag/standard/AbstractColorChangingTag.java
+++ b/text-minimessage/src/main/java/net/kyori/adventure/text/minimessage/tag/standard/AbstractAttributeChangingTag.java
@@ -34,6 +34,9 @@
import net.kyori.adventure.text.VirtualComponent;
import net.kyori.adventure.text.VirtualComponentRenderer;
import net.kyori.adventure.text.flattener.ComponentFlattener;
+import net.kyori.adventure.text.format.ShadowColor;
+import net.kyori.adventure.text.format.Style;
+import net.kyori.adventure.text.format.StyleSetter;
import net.kyori.adventure.text.format.TextColor;
import net.kyori.adventure.text.minimessage.internal.parser.node.TagNode;
import net.kyori.adventure.text.minimessage.internal.parser.node.ValueNode;
@@ -58,9 +61,10 @@
*
(color()? advanceColor())*
*
*
+ * @param the style attribute type
* @since 4.10.0
*/
-abstract class AbstractColorChangingTag implements Modifying, Examinable {
+abstract class AbstractAttributeChangingTag implements Modifying, Examinable {
private static final ComponentFlattener LENGTH_CALCULATOR = ComponentFlattener.builder()
.mapper(TextComponent.class, TextComponent::content)
.unknownMapper(x -> "_") // every unknown component gets a single colour
@@ -106,7 +110,7 @@ public final Component apply(final @NotNull Component current, final int depth)
return Component.virtual(Void.class, new TagInfoHolder(this.preserveData(), current), current.style());
}
- if ((this.disableApplyingColorDepth != -1 && depth > this.disableApplyingColorDepth) || current.style().color() != null) {
+ if ((this.disableApplyingColorDepth != -1 && depth > this.disableApplyingColorDepth) || this.query(current.style()) != null) {
if (this.disableApplyingColorDepth == -1 || depth < this.disableApplyingColorDepth) {
this.disableApplyingColorDepth = depth;
}
@@ -135,15 +139,15 @@ public final Component apply(final @NotNull Component current, final int depth)
final int[] holder = new int[1];
for (final PrimitiveIterator.OfInt it = content.codePoints().iterator(); it.hasNext();) {
holder[0] = it.nextInt();
- final Component comp = Component.text(new String(holder, 0, 1), current.style().color(this.color()));
- this.advanceColor();
+ final Component comp = Component.text(new String(holder, 0, 1), this.apply(current.style(), this.attribute()));
+ this.advanceAttribute();
parent.append(comp);
}
return parent.build();
} else if (!(current instanceof TextComponent)) {
- final Component ret = current.children(Collections.emptyList()).colorIfAbsent(this.color());
- this.advanceColor();
+ final Component ret = this.applyIfAbsent(current.children(Collections.emptyList()), this.attribute());
+ this.advanceAttribute();
return ret;
}
@@ -154,7 +158,7 @@ private void skipColorForLengthOf(final String content) {
final int len = content.codePointCount(0, content.length());
for (int i = 0; i < len; i++) {
// increment our color index
- this.advanceColor();
+ this.advanceAttribute();
}
}
@@ -165,7 +169,7 @@ private void skipColorForLengthOf(final String content) {
/**
* Advance the active color.
*/
- protected abstract void advanceColor();
+ protected abstract void advanceAttribute();
/**
* Get the current color, without side-effects.
@@ -173,7 +177,13 @@ private void skipColorForLengthOf(final String content) {
* @return the current color
* @since 4.10.0
*/
- protected abstract TextColor color();
+ protected abstract S attribute();
+
+ protected abstract > T apply(final T style, final S attribute);
+
+ protected abstract > T applyIfAbsent(final T style, final S attribute);
+
+ protected abstract @Nullable S query(final Style style);
/**
* Return an emitable that will accurately reserialize the provided input data.
@@ -241,4 +251,38 @@ public void emit(final @NotNull TokenEmitter emitter) {
return (TagInfoHolder) holder;
}
+
+ static abstract class OfColor extends AbstractAttributeChangingTag {
+ @Override
+ protected > T apply(final T style, final TextColor attribute) {
+ return style.color(attribute);
+ }
+
+ @Override
+ protected > T applyIfAbsent(final T style, final TextColor attribute) {
+ return style.colorIfAbsent(attribute);
+ }
+
+ @Override
+ protected @Nullable TextColor query(final Style style) {
+ return style.color();
+ }
+ }
+
+ static abstract class OfShadowColor extends AbstractAttributeChangingTag {
+ @Override
+ protected > T apply(final T style, final ShadowColor attribute) {
+ return style.shadowColor(attribute);
+ }
+
+ @Override
+ protected > T applyIfAbsent(final T style, final ShadowColor attribute) {
+ return style.shadowColorIfAbsent(attribute);
+ }
+
+ @Override
+ protected @Nullable ShadowColor query(final Style style) {
+ return style.shadowColor();
+ }
+ }
}
diff --git a/text-minimessage/src/main/java/net/kyori/adventure/text/minimessage/tag/standard/GradientTag.java b/text-minimessage/src/main/java/net/kyori/adventure/text/minimessage/tag/standard/GradientTag.java
index fa191ba3b1..312f3bcc4a 100644
--- a/text-minimessage/src/main/java/net/kyori/adventure/text/minimessage/tag/standard/GradientTag.java
+++ b/text-minimessage/src/main/java/net/kyori/adventure/text/minimessage/tag/standard/GradientTag.java
@@ -49,12 +49,12 @@
*
* @since 4.10.0
*/
-class GradientTag extends AbstractColorChangingTag {
+class GradientTag extends AbstractAttributeChangingTag.OfColor {
private static final String GRADIENT = "gradient";
private static final TextColor DEFAULT_WHITE = TextColor.color(0xffffff);
private static final TextColor DEFAULT_BLACK = TextColor.color(0x000000);
- static final TagResolver RESOLVER = SerializableResolver.claimingComponent(GRADIENT, GradientTag::create, AbstractColorChangingTag::claimComponent);
+ static final TagResolver RESOLVER = SerializableResolver.claimingComponent(GRADIENT, GradientTag::create, AbstractAttributeChangingTag::claimComponent);
private int index = 0;
@@ -125,12 +125,12 @@ protected void init() {
}
@Override
- protected void advanceColor() {
+ protected void advanceAttribute() {
this.index++;
}
@Override
- protected TextColor color() {
+ protected TextColor attribute() {
// from [0, this.colors.length - 1], select the position in the gradient
// we will wrap around in order to preserve an even cycle as would be seen with non-zero phases
final double position = ((this.index * this.multiplier) + this.phase);
diff --git a/text-minimessage/src/main/java/net/kyori/adventure/text/minimessage/tag/standard/GradowTag.java b/text-minimessage/src/main/java/net/kyori/adventure/text/minimessage/tag/standard/GradowTag.java
new file mode 100644
index 0000000000..eea2143e4c
--- /dev/null
+++ b/text-minimessage/src/main/java/net/kyori/adventure/text/minimessage/tag/standard/GradowTag.java
@@ -0,0 +1,204 @@
+/*
+ * This file is part of adventure, licensed under the MIT License.
+ *
+ * Copyright (c) 2017-2024 KyoriPowered
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+package net.kyori.adventure.text.minimessage.tag.standard;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+import java.util.Objects;
+import java.util.OptionalDouble;
+import java.util.function.Consumer;
+import java.util.stream.Stream;
+import net.kyori.adventure.text.format.NamedTextColor;
+import net.kyori.adventure.text.format.ShadowColor;
+import net.kyori.adventure.text.format.TextColor;
+import net.kyori.adventure.text.minimessage.Context;
+import net.kyori.adventure.text.minimessage.internal.serializer.SerializableResolver;
+import net.kyori.adventure.text.minimessage.internal.serializer.TokenEmitter;
+import net.kyori.adventure.text.minimessage.tag.Tag;
+import net.kyori.adventure.text.minimessage.tag.resolver.ArgumentQueue;
+import net.kyori.adventure.text.minimessage.tag.resolver.TagResolver;
+import net.kyori.examination.ExaminableProperty;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+import org.jetbrains.annotations.Range;
+
+/**
+ * A transformation that applies a colour gradient.
+ *
+ * @since 4.19.0
+ */
+class GradowTag extends AbstractAttributeChangingTag.OfShadowColor {
+ private static final String GRADOW = "gradow";
+ private static final int SHADOW_ALPHA = 0xcc;
+ private static final ShadowColor DEFAULT_WHITE = ShadowColor.shadowColor(0xccffffff);
+ private static final ShadowColor DEFAULT_BLACK = ShadowColor.shadowColor(0xcc000000);
+
+ static final TagResolver RESOLVER = SerializableResolver.claimingComponent(GRADOW, GradowTag::create, AbstractAttributeChangingTag::claimComponent);
+
+ private int index = 0;
+
+ private double multiplier = 1;
+
+ private final ShadowColor[] colors;
+ @Range(from = -1, to = 1) double phase;
+
+ private final boolean negativePhase;
+
+ static Tag create(final ArgumentQueue args, final Context ctx) {
+ double phase = 0;
+ final List textColors;
+ if (args.hasNext()) {
+ textColors = new ArrayList<>();
+ while (args.hasNext()) {
+ final Argument arg = args.pop();
+ // last argument? maybe this is the phase?
+ if (!args.hasNext()) {
+ final OptionalDouble possiblePhase = arg.asDouble();
+ if (possiblePhase.isPresent()) {
+ phase = possiblePhase.getAsDouble();
+ if (phase < -1d || phase > 1d) {
+ throw ctx.newException(String.format("Shadow gradient phase is out of range (%s). Must be in the range [-1.0, 1.0] (inclusive).", phase), args);
+ }
+ break;
+ }
+ }
+
+ final ShadowColor parsedColor = ShadowColor.shadowColor(ColorTagResolver.resolveColor(arg.value(), ctx), SHADOW_ALPHA);
+ textColors.add(parsedColor);
+ }
+
+ if (textColors.size() == 1) {
+ throw ctx.newException("Invalid gradient, not enough colors. Gradients must have at least two colors.", args);
+ }
+ } else {
+ textColors = Collections.emptyList();
+ }
+
+ return new GradowTag(phase, textColors);
+ }
+
+ GradowTag(final double phase, final List colors) {
+ if (colors.isEmpty()) {
+ this.colors = new ShadowColor[]{DEFAULT_WHITE, DEFAULT_BLACK};
+ } else {
+ this.colors = colors.toArray(new ShadowColor[0]);
+ }
+
+ if (phase < 0) {
+ this.negativePhase = true;
+ this.phase = 1 + phase; // [-1, 0) -> [0, 1)
+ Collections.reverse(Arrays.asList(this.colors));
+ } else {
+ this.negativePhase = false;
+ this.phase = phase;
+ }
+ }
+
+ @Override
+ protected void init() {
+ // Set a scaling factor for character indices, so that the colours in a gradient are evenly spread across the original text
+ // make it so the max character index maps to the maximum colour
+ this.multiplier = this.size() == 1 ? 0 : (double) (this.colors.length - 1) / (this.size() - 1);
+ this.phase *= this.colors.length - 1;
+ this.index = 0;
+ }
+
+ @Override
+ protected void advanceAttribute() {
+ this.index++;
+ }
+
+ @Override
+ protected ShadowColor attribute() {
+ // from [0, this.colors.length - 1], select the position in the gradient
+ // we will wrap around in order to preserve an even cycle as would be seen with non-zero phases
+ final double position = ((this.index * this.multiplier) + this.phase);
+ final int lowUnclamped = (int) Math.floor(position);
+
+ final int high = (int) Math.ceil(position) % this.colors.length;
+ final int low = lowUnclamped % this.colors.length;
+
+ return ShadowColor.lerp((float) position - lowUnclamped, this.colors[low], this.colors[high]);
+ }
+
+ @Override
+ protected @NotNull Consumer preserveData() {
+ final ShadowColor[] colors;
+ final double phase;
+
+ if (this.negativePhase) {
+ colors = Arrays.copyOf(this.colors, this.colors.length);
+ Collections.reverse(Arrays.asList(colors));
+ phase = this.phase - 1;
+ } else {
+ colors = this.colors;
+ phase = this.phase;
+ }
+
+ return emit -> {
+ emit.tag(GRADOW);
+ if (colors.length != 2 || !colors[0].equals(DEFAULT_WHITE) || !colors[1].equals(DEFAULT_BLACK)) { // non-default params
+ for (final ShadowColor shadow : colors) {
+ final TextColor color = TextColor.color(shadow);
+ if (color instanceof NamedTextColor) {
+ emit.argument(NamedTextColor.NAMES.keyOrThrow((NamedTextColor) color));
+ } else {
+ emit.argument(color.asHexString());
+ }
+ }
+ }
+
+ if (phase != 0) {
+ emit.argument(Double.toString(phase));
+ }
+ };
+ }
+
+ @Override
+ public @NotNull Stream extends ExaminableProperty> examinableProperties() {
+ return Stream.of(
+ ExaminableProperty.of("phase", this.phase),
+ ExaminableProperty.of("colors", this.colors)
+ );
+ }
+
+ @Override
+ public boolean equals(final @Nullable Object other) {
+ if (this == other) return true;
+ if (other == null || this.getClass() != other.getClass()) return false;
+ final GradowTag that = (GradowTag) other;
+ return this.index == that.index
+ && this.phase == that.phase
+ && Arrays.equals(this.colors, that.colors);
+ }
+
+ @Override
+ public int hashCode() {
+ int result = Objects.hash(this.index, this.phase);
+ result = 31 * result + Arrays.hashCode(this.colors);
+ return result;
+ }
+}
diff --git a/text-minimessage/src/main/java/net/kyori/adventure/text/minimessage/tag/standard/RainbowTag.java b/text-minimessage/src/main/java/net/kyori/adventure/text/minimessage/tag/standard/RainbowTag.java
index 17018ee90c..cc5e30f698 100644
--- a/text-minimessage/src/main/java/net/kyori/adventure/text/minimessage/tag/standard/RainbowTag.java
+++ b/text-minimessage/src/main/java/net/kyori/adventure/text/minimessage/tag/standard/RainbowTag.java
@@ -43,11 +43,11 @@
*
* @since 4.10.0
*/
-final class RainbowTag extends AbstractColorChangingTag {
+final class RainbowTag extends AbstractAttributeChangingTag.OfColor {
private static final String REVERSE = "!";
private static final String RAINBOW = "rainbow";
- static final TagResolver RESOLVER = SerializableResolver.claimingComponent(RAINBOW, RainbowTag::create, AbstractColorChangingTag::claimComponent);
+ static final TagResolver RESOLVER = SerializableResolver.claimingComponent(RAINBOW, RainbowTag::create, AbstractAttributeChangingTag::claimComponent);
private final boolean reversed;
private final double dividedPhase;
@@ -89,7 +89,7 @@ protected void init() {
}
@Override
- protected void advanceColor() {
+ protected void advanceAttribute() {
if (this.reversed) {
if (this.colorIndex == 0) {
this.colorIndex = this.size() - 1;
@@ -102,7 +102,7 @@ protected void advanceColor() {
}
@Override
- protected TextColor color() {
+ protected TextColor attribute() {
final float index = this.colorIndex;
final float hue = (float) ((index / this.size() + this.dividedPhase) % 1f);
return TextColor.color(HSVLike.hsvLike(hue, 1f, 1f));
diff --git a/text-minimessage/src/main/java/net/kyori/adventure/text/minimessage/tag/standard/ShainbowTag.java b/text-minimessage/src/main/java/net/kyori/adventure/text/minimessage/tag/standard/ShainbowTag.java
new file mode 100644
index 0000000000..b3ca51988f
--- /dev/null
+++ b/text-minimessage/src/main/java/net/kyori/adventure/text/minimessage/tag/standard/ShainbowTag.java
@@ -0,0 +1,146 @@
+/*
+ * This file is part of adventure, licensed under the MIT License.
+ *
+ * Copyright (c) 2017-2024 KyoriPowered
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+package net.kyori.adventure.text.minimessage.tag.standard;
+
+import java.util.Objects;
+import java.util.function.Consumer;
+import java.util.stream.Stream;
+import net.kyori.adventure.text.format.ShadowColor;
+import net.kyori.adventure.text.format.TextColor;
+import net.kyori.adventure.text.minimessage.Context;
+import net.kyori.adventure.text.minimessage.internal.serializer.SerializableResolver;
+import net.kyori.adventure.text.minimessage.internal.serializer.TokenEmitter;
+import net.kyori.adventure.text.minimessage.tag.Tag;
+import net.kyori.adventure.text.minimessage.tag.resolver.ArgumentQueue;
+import net.kyori.adventure.text.minimessage.tag.resolver.TagResolver;
+import net.kyori.adventure.util.HSVLike;
+import net.kyori.examination.ExaminableProperty;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+/**
+ * Applies rainbow color to a component.
+ *
+ * @since 4.19.0
+ */
+final class ShainbowTag extends AbstractAttributeChangingTag.OfShadowColor {
+ private static final String REVERSE = "!";
+ private static final String RAINBOW = "shainbow";
+ private static final int SHADOW_ALPHA = 0xcc;
+
+ static final TagResolver RESOLVER = SerializableResolver.claimingComponent(RAINBOW, ShainbowTag::create, AbstractAttributeChangingTag::claimComponent);
+
+ private final boolean reversed;
+ private final double dividedPhase;
+
+ private int colorIndex = 0;
+
+ static Tag create(final ArgumentQueue args, final Context ctx) {
+ boolean reversed = false;
+ int phase = 0;
+
+ if (args.hasNext()) {
+ String value = args.pop().value();
+ if (value.startsWith(REVERSE)) {
+ reversed = true;
+ value = value.substring(REVERSE.length());
+ }
+ if (value.length() > 0) {
+ try {
+ phase = Integer.parseInt(value);
+ } catch (final NumberFormatException ex) {
+ throw ctx.newException("Expected phase, got " + value);
+ }
+ }
+ }
+
+ return new ShainbowTag(reversed, phase);
+ }
+
+ private ShainbowTag(final boolean reversed, final int phase) {
+ this.reversed = reversed;
+ this.dividedPhase = ((double) phase) / 10d;
+ }
+
+ @Override
+ protected void init() {
+ if (this.reversed) {
+ this.colorIndex = this.size() - 1;
+ }
+ }
+
+ @Override
+ protected void advanceAttribute() {
+ if (this.reversed) {
+ if (this.colorIndex == 0) {
+ this.colorIndex = this.size() - 1;
+ } else {
+ this.colorIndex--;
+ }
+ } else {
+ this.colorIndex++;
+ }
+ }
+
+ @Override
+ protected ShadowColor attribute() {
+ final float index = this.colorIndex;
+ final float hue = (float) ((index / this.size() + this.dividedPhase) % 1f);
+ return ShadowColor.shadowColor(TextColor.color(HSVLike.hsvLike(hue, 1f, 1f)), SHADOW_ALPHA);
+ }
+
+ @Override
+ protected @NotNull Consumer preserveData() {
+ final boolean reversed = this.reversed;
+ final int phase = (int) Math.round(this.dividedPhase * 10);
+ return emit -> {
+ emit.tag(RAINBOW);
+ if (reversed && phase != 0) {
+ emit.argument(REVERSE + phase);
+ } else if (reversed) {
+ emit.argument(REVERSE);
+ } else if (phase != 0) {
+ emit.argument(Integer.toString(phase));
+ }
+ };
+ }
+
+ @Override
+ public @NotNull Stream extends ExaminableProperty> examinableProperties() {
+ return Stream.of(ExaminableProperty.of("phase", this.dividedPhase));
+ }
+
+ @Override
+ public boolean equals(final @Nullable Object other) {
+ if (this == other) return true;
+ if (other == null || this.getClass() != other.getClass()) return false;
+ final ShainbowTag that = (ShainbowTag) other;
+ return this.colorIndex == that.colorIndex && this.dividedPhase == that.dividedPhase;
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(this.colorIndex, this.dividedPhase);
+ }
+}
diff --git a/text-minimessage/src/main/java/net/kyori/adventure/text/minimessage/tag/standard/StandardTags.java b/text-minimessage/src/main/java/net/kyori/adventure/text/minimessage/tag/standard/StandardTags.java
index 3b36e851f4..9f7991ba7a 100644
--- a/text-minimessage/src/main/java/net/kyori/adventure/text/minimessage/tag/standard/StandardTags.java
+++ b/text-minimessage/src/main/java/net/kyori/adventure/text/minimessage/tag/standard/StandardTags.java
@@ -65,7 +65,9 @@ private StandardTags() {
ScoreTag.RESOLVER,
NbtTag.RESOLVER,
PrideTag.RESOLVER,
- ShadowColorTag.RESOLVER
+ ShadowColorTag.RESOLVER,
+ GradowTag.RESOLVER,
+ ShainbowTag.RESOLVER
)
.build();
@@ -288,6 +290,26 @@ public static TagResolver transition() {
return ShadowColorTag.RESOLVER;
}
+ /**
+ * Get a resolver for the {@value ShainbowTag#RAINBOW} tags.
+ *
+ * @return a resolver for the {@value ShainbowTag#RAINBOW} tags
+ * @since 4.18.0
+ */
+ public static @NotNull TagResolver shainhow() {
+ return ShainbowTag.RESOLVER;
+ }
+
+ /**
+ * Get a resolver for the {@value GradowTag#GRADOW} tags.
+ *
+ * @return a resolver for the {@value GradowTag#GRADOW} tags
+ * @since 4.18.0
+ */
+ public static @NotNull TagResolver gradow() {
+ return GradowTag.RESOLVER;
+ }
+
/**
* Get a resolver that handles all default standard tags.
*