4 changes: 4 additions & 0 deletions docs/user/default-placeholders.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,10 @@ Prior to 1.19, arguments were separated with a slash (`/`) instead of space.
- `%player:pos_x%` - The player's `x` coordinate.
- `%player:pos_y%` - The player's `y` coordinate.
- `%player:pos_z%` - The player's `z` coordinate.
- `%player:facing%` (2.5.1+) - The player's horizontal direction.
- `%player:facing_axis%` (2.5.1+) - The player's horizontal direction in format of `+/-A` (A si replaced with axis).
- `%player:horizontal_facing%` (2.5.1+) - The player's horizontal facing direction.
- `%player:horizontal_facing_axis%` (2.5.1+) - The player's horizontal facing direction in format of `+/-A` (A si replaced with axis).
- `%player:health%` - The player's health.
- `%player:max_health%` - The player's max health.
- `%player:hunger%` - The player's hunger.
Expand Down
10 changes: 5 additions & 5 deletions gradle.properties
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,15 @@ org.gradle.jvmargs=-Xmx1G

# Fabric Properties
# check these on https://fabricmc.net/use
minecraft_version=1.21.2-rc1
yarn_mappings=1.21.2-rc1+build.1
loader_version=0.16.7
minecraft_version=1.21.3
yarn_mappings=1.21.3+build.1
loader_version=0.16.9

#Fabric api
fabric_version=0.105.4+1.21.2
fabric_version=0.106.1+1.21.3

# Mod Properties
mod_version = 2.5.0+1.21.2
mod_version = 2.5.1+1.21.3
maven_group = eu.pb4
archives_base_name = placeholder-api

12 changes: 12 additions & 0 deletions src/main/java/eu/pb4/placeholders/api/ParserContext.java
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
package eu.pb4.placeholders.api;

import net.minecraft.registry.RegistryWrapper;
import org.jetbrains.annotations.Nullable;

import java.util.HashMap;
import java.util.Map;
import java.util.Objects;

public final class ParserContext {
private final Map<Key<?>, Object> map = new HashMap<>();
Expand All @@ -27,11 +29,21 @@ public <T> ParserContext with(Key<T> key, T object) {
public <T> T get(Key<T> key) {
//noinspection unchecked
return (T) this.map.get(key);
}

public <T> T getOrThrow(Key<T> key) {
//noinspection unchecked
return Objects.requireNonNull((T) this.map.get(key));
};

public boolean contains(Key<?> key) {
return this.map.containsKey(key);
}


public record Key<T>(String key, @Nullable Class<T> type) {
public static final Key<Boolean> COMPACT_TEXT = new Key<>("compact_text", Boolean.class);
public static final Key<RegistryWrapper.WrapperLookup> WRAPPER_LOOKUP = new Key<>("wrapper_lookup", RegistryWrapper.WrapperLookup.class);

public static <T> Key<T> of(String key, T type) {
//noinspection unchecked
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,12 @@
import net.minecraft.util.Identifier;
import net.minecraft.util.math.Vec2f;
import net.minecraft.util.math.Vec3d;
import org.jetbrains.annotations.Contract;
import org.jetbrains.annotations.Nullable;

import java.util.function.Supplier;


public record PlaceholderContext(MinecraftServer server,
Supplier<ServerCommandSource> lazySource,
@Nullable ServerWorld world,
Expand Down Expand Up @@ -69,7 +71,7 @@ public boolean hasEntity() {
}

public ParserContext asParserContext() {
return ParserContext.of(KEY, this);
return ParserContext.of(KEY, this).with(ParserContext.Key.WRAPPER_LOOKUP, this.server.getRegistryManager());
}

public PlaceholderContext withView(ViewObject view) {
Expand All @@ -78,6 +80,7 @@ public PlaceholderContext withView(ViewObject view) {

public void addToContext(ParserContext context) {
context.with(KEY, this);
context.with(ParserContext.Key.WRAPPER_LOOKUP, this.server.getRegistryManager());
}


Expand Down
26 changes: 21 additions & 5 deletions src/main/java/eu/pb4/placeholders/api/arguments/StringArgs.java
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ public final class StringArgs {
private static final StringArgs EMPTY = new StringArgs("");
private final List<String> ordered = new ArrayList<>();
private final Map<String, String> keyed = new HashMap<>();
private Map<String, StringArgs> keyedMaps = new HashMap<>();
private final Map<String, StringArgs> keyedMaps = new HashMap<>();
private final String input;
private int currentOrdered = 0;

Expand Down Expand Up @@ -69,20 +69,27 @@ private static int keyDecomposition(String input, int offset, char separator, ch
if (chr == stopAt && wrap == 0) {
break;
} else if (key != null && b.isEmpty() && hasMaps && (chr == '{' || chr == '[') && wrap == 0) {
var arg = new StringArgs("");
var ordered = new ArrayList<String>();
var keyed = new HashMap<String, String>();
var keyedMaps = new HashMap<String, StringArgs>();
var ti = keyDecomposition(input, i + 1, separator, map, isWrap, true,
chr == '{' ? '}' : ']', (keyx, valuex) -> {
if (keyx != null) {
arg.keyed.put(keyx, valuex != null ? SimpleArguments.unwrap(valuex, isWrap) : "");
keyed.put(keyx, valuex != null ? SimpleArguments.unwrap(valuex, isWrap) : "");

if (valuex == null) {
arg.ordered.add(SimpleArguments.unwrap(keyx, isWrap));
ordered.add(SimpleArguments.unwrap(keyx, isWrap));
}
}
}, arg.keyedMaps::put);
}, keyedMaps::put);
if (ti == input.length()) {
b.append(chr);
} else {
var arg = new StringArgs(input.substring(i, ti));
arg.ordered.addAll(ordered);
arg.keyed.putAll(keyed);
arg.keyedMaps.putAll(keyedMaps);

mapConsumer.accept(key, arg);
key = null;
i = ti;
Expand Down Expand Up @@ -142,6 +149,15 @@ public String get(String name) {
return this.keyed.get(name);
}

@Nullable
public StringArgs getNested(String name) {
return this.keyedMaps.get(name);
}

public StringArgs getNestedOrEmpty(String name) {
return this.keyedMaps.getOrDefault(name, EMPTY);
}

public String get(String name, String defaultValue) {
return this.keyed.getOrDefault(name, defaultValue);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -103,7 +103,7 @@ static GradientProvider colorsOkLab(List<TextColor> colors) {
if (hvs.isEmpty()) {
hvs.add(new OkLab(1, 1, 1));
} else if (hvs.size() == 1) {
hvs.add(hvs.get(0));
hvs.add(hvs.getFirst());
}

final int colorSize = hvs.size();
Expand Down
34 changes: 32 additions & 2 deletions src/main/java/eu/pb4/placeholders/api/node/parent/HoverNode.java
Original file line number Diff line number Diff line change
@@ -1,12 +1,20 @@
package eu.pb4.placeholders.api.node.parent;

import com.mojang.serialization.DynamicOps;
import eu.pb4.placeholders.api.ParserContext;
import eu.pb4.placeholders.api.PlaceholderContext;
import eu.pb4.placeholders.api.node.TextNode;
import eu.pb4.placeholders.api.parsers.NodeParser;
import net.minecraft.component.ComponentChanges;
import net.minecraft.entity.EntityType;
import net.minecraft.item.ItemStack;
import net.minecraft.registry.RegistryKey;
import net.minecraft.registry.RegistryKeys;
import net.minecraft.registry.RegistryWrapper;
import net.minecraft.text.HoverEvent;
import net.minecraft.text.Style;
import net.minecraft.text.Text;
import net.minecraft.util.Identifier;
import org.jetbrains.annotations.Nullable;

import java.util.Arrays;
Expand All @@ -28,6 +36,17 @@ protected Style style(ParserContext context) {
return Style.EMPTY.withHoverEvent(new HoverEvent((HoverEvent.Action<Object>) this.action.vanillaType(), ((TextNode) this.value).toText(context, true)));
} else if (this.action == Action.ENTITY) {
return Style.EMPTY.withHoverEvent(new HoverEvent((HoverEvent.Action<Object>) this.action.vanillaType(), ((EntityNodeContent) this.value).toVanilla(context)));
} else if (this.action == Action.LAZY_ITEM_STACK) {
RegistryWrapper.WrapperLookup wrapper;
if (context.contains(ParserContext.Key.WRAPPER_LOOKUP)) {
wrapper = context.getOrThrow(ParserContext.Key.WRAPPER_LOOKUP);
} else if (context.contains(PlaceholderContext.KEY)) {
wrapper = context.getOrThrow(PlaceholderContext.KEY).server().getRegistryManager();
} else {
return Style.EMPTY;
}

return Style.EMPTY.withHoverEvent(new HoverEvent(HoverEvent.Action.SHOW_ITEM, ((LazyItemStackNodeContent) this.value).toVanilla(wrapper)));
} else {
return Style.EMPTY.withHoverEvent(new HoverEvent((HoverEvent.Action<Object>) this.action.vanillaType(), this.value));
}
Expand Down Expand Up @@ -68,18 +87,29 @@ public String toString() {

@Override
public boolean isDynamicNoChildren() {
return (this.action == Action.TEXT && ((TextNode) this.value).isDynamic()) || (this.action == Action.ENTITY && ((EntityNodeContent) this.value).name.isDynamic());
return (this.action == Action.TEXT && ((TextNode) this.value).isDynamic()) || (this.action == Action.ENTITY && ((EntityNodeContent) this.value).name.isDynamic()) || this.action == Action.LAZY_ITEM_STACK;
}

public record Action<T, H>(HoverEvent.Action<H> vanillaType) {
public static final Action<EntityNodeContent, HoverEvent.EntityContent> ENTITY = new Action<>(HoverEvent.Action.SHOW_ENTITY);
public static final Action<HoverEvent.ItemStackContent, HoverEvent.ItemStackContent> ITEM_STACK = new Action<>(HoverEvent.Action.SHOW_ITEM);
public static final Action<TextNode, Text> TEXT = new Action<>(HoverEvent.Action.SHOW_TEXT);

public static final Action<HoverEvent.ItemStackContent, HoverEvent.ItemStackContent> ITEM_STACK = new Action<>(HoverEvent.Action.SHOW_ITEM);
public static final Action<LazyItemStackNodeContent, HoverEvent.ItemStackContent> LAZY_ITEM_STACK = new Action<>(HoverEvent.Action.SHOW_ITEM);
}

public record EntityNodeContent(EntityType<?>entityType, UUID uuid, @Nullable TextNode name) {
public HoverEvent.EntityContent toVanilla(ParserContext context) {
return new HoverEvent.EntityContent(this.entityType, this.uuid, this.name != null ? this.name.toText(context, true) : null);
}
}

public record LazyItemStackNodeContent<T>(Identifier identifier, int count, DynamicOps<T> ops, T componentMap) {
public HoverEvent.ItemStackContent toVanilla(RegistryWrapper.WrapperLookup lookup) {
var stack = new ItemStack(lookup.getOrThrow(RegistryKeys.ITEM).getOrThrow(RegistryKey.of(RegistryKeys.ITEM, identifier)));
stack.setCount(count);
stack.applyChanges(ComponentChanges.CODEC.decode(lookup.getOps(ops), componentMap).getOrThrow().getFirst());
return new HoverEvent.ItemStackContent(stack);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ public final Text toText(ParserContext context, boolean removeBackslashes) {
return out;
}

return ((MutableText) this.applyFormatting(out.copy(), context));
return this.applyFormatting(out.copy(), context);
} else {
MutableText base = compact ? null : Text.empty();

Expand Down
4 changes: 3 additions & 1 deletion src/main/java/eu/pb4/placeholders/impl/GeneralUtils.java
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,9 @@ public static MutableText toGradient(Text base, GradientNode.GradientProvider po
}

private static int getGradientLength(Text base) {
int length = base.getContent() instanceof PlainTextContent.Literal l ? l.string().length() : base.getContent() == PlainTextContent.EMPTY ? 0 : 1;
int length = base.getContent() instanceof PlainTextContent.Literal l
? l.string().codePointCount(0, l.string().length())
: base.getContent() == PlainTextContent.EMPTY ? 0 : 1;

for (var text : base.getSiblings()) {
length += getGradientLength(text);
Expand Down
4 changes: 1 addition & 3 deletions src/main/java/eu/pb4/placeholders/impl/StringArgOps.java
Original file line number Diff line number Diff line change
@@ -1,11 +1,9 @@
package eu.pb4.placeholders.impl;

import com.google.gson.JsonElement;
import com.mojang.datafixers.util.Either;
import com.mojang.datafixers.util.Pair;
import com.mojang.serialization.DataResult;
import com.mojang.serialization.DynamicOps;
import com.mojang.serialization.JsonOps;
import eu.pb4.placeholders.api.arguments.StringArgs;

import java.util.stream.Stream;
Expand Down Expand Up @@ -42,7 +40,7 @@ public Either<String, StringArgs> createNumeric(Number i) {

@Override
public DataResult<String> getStringValue(Either<String, StringArgs> input) {
return input.left().isPresent() ? DataResult.success(input.left().get()) : DataResult.error(() -> input + " is not a number!");
return input.left().isPresent() ? DataResult.success(input.left().get()) : DataResult.error(() -> input + " is not a string!");
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,11 @@
import net.minecraft.text.Text;
import net.minecraft.util.Formatting;
import net.minecraft.util.Identifier;
import net.minecraft.util.math.Direction;
import org.apache.commons.lang3.time.DurationFormatUtils;

import java.util.Locale;


public class PlayerPlaceholders {
public static void register() {
Expand Down Expand Up @@ -282,6 +285,42 @@ public static void register() {
}
});

Placeholders.register(Identifier.of("player", "facing"), (ctx, arg) -> {
if (ctx.hasPlayer()) {
return PlaceholderResult.value(ctx.player().getFacing().asString());
} else {
return PlaceholderResult.invalid("No player!");
}
});

Placeholders.register(Identifier.of("player", "facing_axis"), (ctx, arg) -> {
if (ctx.hasPlayer()) {
var facing = ctx.player().getFacing();
return PlaceholderResult.value(
(facing.getDirection() == Direction.AxisDirection.NEGATIVE ? "-" : "+") + facing.getAxis().asString().toUpperCase(Locale.ROOT));
} else {
return PlaceholderResult.invalid("No player!");
}
});

Placeholders.register(Identifier.of("player", "horizontal_facing"), (ctx, arg) -> {
if (ctx.hasPlayer()) {
return PlaceholderResult.value(ctx.player().getHorizontalFacing().asString());
} else {
return PlaceholderResult.invalid("No player!");
}
});

Placeholders.register(Identifier.of("player", "horizontal_facing_axis"), (ctx, arg) -> {
if (ctx.hasPlayer()) {
var facing = ctx.player().getHorizontalFacing();
return PlaceholderResult.value(
(facing.getDirection() == Direction.AxisDirection.NEGATIVE ? "-" : "+") + facing.getAxis().asString().toUpperCase(Locale.ROOT));
} else {
return PlaceholderResult.invalid("No player!");
}
});

Placeholders.register(Identifier.of("player", "pos_x"), (ctx, arg) -> {
if (ctx.hasPlayer()) {
double value = ctx.player().getX();
Expand Down
34 changes: 19 additions & 15 deletions src/main/java/eu/pb4/placeholders/impl/textparser/BuiltinTags.java
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,8 @@
import eu.pb4.placeholders.impl.GeneralUtils;
import eu.pb4.placeholders.impl.StringArgOps;
import net.minecraft.entity.EntityType;
import net.minecraft.item.ItemStack;
import net.minecraft.nbt.NbtOps;
import net.minecraft.nbt.StringNbtReader;
import net.minecraft.registry.DynamicRegistryManager;
import net.minecraft.registry.Registries;
import net.minecraft.text.*;
import net.minecraft.util.Formatting;
import net.minecraft.util.Identifier;
Expand Down Expand Up @@ -321,23 +319,29 @@ public static void register() {
} else if (type.equals("show_item") || type.equals("item")) {
var value = data.getNext("value", "");
try {
return new HoverNode<>(nodes,
HoverNode.Action.ITEM_STACK,
new HoverEvent.ItemStackContent(ItemStack.fromNbtOrEmpty(DynamicRegistryManager.EMPTY, StringNbtReader.parse(value)))
);
} catch (Throwable e) {
var stack = Registries.ITEM.get(Identifier.tryParse(data.get("item", value))).getDefaultStack();
var nbt = StringNbtReader.parse(value);
var id = Identifier.of(nbt.getString("id"));
var count = nbt.contains("count") ? nbt.getInt("count") : 1;

var count = data.getNext("count");
if (count != null) {
stack.setCount(Integer.parseInt(count));
var comps = nbt.getCompound("components");
return new HoverNode<>(nodes,
HoverNode.Action.LAZY_ITEM_STACK,
new HoverNode.LazyItemStackNodeContent<>(id, count, NbtOps.INSTANCE, comps));
} catch (Throwable ignored) {}
try {
var item = Identifier.of(data.get("item", value));
var count = 1;
var countTxt = data.getNext("count");
if (countTxt != null) {
count = Integer.parseInt(countTxt);
}

return new HoverNode<>(nodes,
HoverNode.Action.ITEM_STACK,
new HoverEvent.ItemStackContent(stack)
HoverNode.Action.LAZY_ITEM_STACK,
new HoverNode.LazyItemStackNodeContent<>(item, count,
StringArgOps.INSTANCE, Either.right(data.getNestedOrEmpty("components")))
);
}
} catch (Throwable ignored) {}
} else {
return new HoverNode<>(nodes, HoverNode.Action.TEXT, parser.parseNode(data.get("value", type)));
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,14 +1,8 @@
package eu.pb4.placeholders.impl.textparser;

import com.google.gson.JsonElement;
import com.mojang.datafixers.util.Either;
import com.mojang.serialization.DataResult;
import com.mojang.serialization.JsonOps;
import eu.pb4.placeholders.api.node.LiteralNode;
import eu.pb4.placeholders.api.node.TextNode;
import eu.pb4.placeholders.api.parsers.TextParserV1;
import io.netty.util.internal.UnstableApi;
import net.minecraft.text.*;
import org.jetbrains.annotations.ApiStatus;

import java.util.ArrayList;
Expand Down