2 changes: 1 addition & 1 deletion build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -165,7 +165,7 @@ curseforge {
releaseType = "release"
changelog = System.getenv("CHANGELOG")
changelogType = "markdown"
addGameVersion((project.minecraft_version.contains("-") ? ((String) project.minecraft_version.split("-")[0] + "-Snapshot") : project.minecraft_version))
addGameVersion((project.minecraft_version.contains("-") ? ((String) project.minecraft_version.split("-")[0] + "-snapshot") : project.minecraft_version))
addGameVersion "Fabric"
addGameVersion "Quilt"
mainArtifact(remapJar)
Expand Down
1 change: 1 addition & 0 deletions docs/user/default-placeholders.md
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,7 @@ Prior to 1.19, arguments were separated with a slash (`/`) instead of space.
- `%server:mod_description [modid]%` - Returns description of the specified mod.
- `%server:objective_name_top [objective] [position]%` - Shows name of the player at the `position`th place in the scoreboard `objective`.
- `%server:objective_score_top [objective] [position]%` - Shows score of the player at the `position`th place in the scoreboard `objective`.
- `%server:objective_score_player [objective] [player]%` - Shows score of the specified `player` in the scoreboard `objective`.

*[TPS]: Ticks Per Second. The number of ticks per second executing on the server. <20 TPS means the server is lagging.
*[MSPT]: Milliseconds Per Tick. The number of milliseconds it takes for a tick on the server. >50 MSPT means the server is lagging.
10 changes: 5 additions & 5 deletions gradle.properties
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,14 @@ org.gradle.jvmargs=-Xmx1G

# Fabric Properties
# check these on https://fabricmc.net/use
minecraft_version=1.21.5
yarn_mappings=1.21.5+build.1
loader_version=0.16.10
minecraft_version=1.21.6-pre2
yarn_mappings=1.21.6-pre2+build.1
loader_version=0.16.14

#Fabric api
fabric_version=0.119.1+1.21.5
fabric_version=0.125.2+1.21.6

# Mod Properties
mod_version = 2.6.3+1.21.5
mod_version = 2.7.0+1.21.6
maven_group = eu.pb4
archives_base_name = placeholder-api
Original file line number Diff line number Diff line change
Expand Up @@ -106,7 +106,7 @@ public static PlaceholderContext of(ServerPlayerEntity player) {
}

public static PlaceholderContext of(ServerPlayerEntity player, ViewObject view) {
return new PlaceholderContext(player.getServer(), player::getCommandSource, player.getServerWorld(), player, player, player.getGameProfile(), view);
return new PlaceholderContext(player.getServer(), player::getCommandSource, player.getWorld(), player, player, player.getGameProfile(), view);
}

public static PlaceholderContext of(ServerCommandSource source) {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,21 +1,45 @@
package eu.pb4.placeholders.api.node.parent;

import com.google.gson.JsonParser;
import com.mojang.datafixers.util.Either;
import com.mojang.serialization.JsonOps;
import eu.pb4.placeholders.api.ParserContext;
import eu.pb4.placeholders.api.PlaceholderContext;
import eu.pb4.placeholders.api.arguments.StringArgs;
import eu.pb4.placeholders.api.node.TextNode;
import eu.pb4.placeholders.api.parsers.NodeParser;
import eu.pb4.placeholders.impl.GeneralUtils;
import eu.pb4.placeholders.impl.StringArgOps;
import net.minecraft.dialog.type.Dialog;
import net.minecraft.nbt.NbtOps;
import net.minecraft.nbt.SnbtOperation;
import net.minecraft.nbt.SnbtParsing;
import net.minecraft.nbt.StringNbtReader;
import net.minecraft.registry.RegistryKey;
import net.minecraft.registry.RegistryKeys;
import net.minecraft.registry.RegistryWrapper;
import net.minecraft.registry.entry.RegistryEntry;
import net.minecraft.text.ClickEvent;
import net.minecraft.text.Style;
import net.minecraft.util.Identifier;
import org.jetbrains.annotations.Nullable;

import java.net.URI;
import java.util.Optional;

public final class ClickActionNode extends SimpleStylingNode {
private final ClickEvent.Action action;
private final TextNode value;
private final @Nullable Either<TextNode, StringArgs> data;

public ClickActionNode(TextNode[] children, ClickEvent.Action action, TextNode value) {
this(children, action, value, null);
}
public ClickActionNode(TextNode[] children, ClickEvent.Action action, TextNode value, @Nullable Either<TextNode, StringArgs> data) {
super(children);
this.action = action;
this.value = value;
this.data = data;
}

public ClickEvent.Action clickEventAction() {
Expand Down Expand Up @@ -47,29 +71,88 @@ protected Style style(ParserContext context) {
case RUN_COMMAND -> Style.EMPTY.withClickEvent(new ClickEvent.RunCommand(this.value.toText(context).getString()));
case SUGGEST_COMMAND -> Style.EMPTY.withClickEvent(new ClickEvent.SuggestCommand(this.value.toText(context).getString()));
case COPY_TO_CLIPBOARD -> Style.EMPTY.withClickEvent(new ClickEvent.CopyToClipboard(this.value.toText(context).getString()));
case CUSTOM -> {
try {
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 {
wrapper = GeneralUtils.DEFAULT_WRAPPER;
}

yield Style.EMPTY.withClickEvent(new ClickEvent.Custom(
Identifier.of(this.value.toText(context).getString()),
this.data == null ? Optional.empty() : Optional.of(data.left().isPresent()
? StringNbtReader.fromOps(wrapper.getOps(NbtOps.INSTANCE)).read(this.data.left().orElseThrow().toText(context).getString())
: StringArgOps.INSTANCE.convertTo(NbtOps.INSTANCE, Either.right(this.data.right().orElseThrow()))
)
));
} catch (Throwable e) {
yield Style.EMPTY;
}

}
case SHOW_DIALOG -> {
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 {
wrapper = GeneralUtils.DEFAULT_WRAPPER;
}
RegistryEntry<Dialog> dialogRegistryEntry = null;
var data = this.value.toText(context).getString();

var id = Identifier.tryParse(data);

if (id != null) {
dialogRegistryEntry = wrapper.getOptionalEntry(RegistryKey.of(RegistryKeys.DIALOG, id)).orElse(null);
}

if (dialogRegistryEntry == null) {
try {
dialogRegistryEntry = Dialog.ENTRY_CODEC.decode(
wrapper.getOps(JsonOps.INSTANCE), JsonParser.parseString(data)).getOrThrow().getFirst();
} catch (Throwable e) {
// ignored
}

}

if (dialogRegistryEntry != null) {
yield Style.EMPTY.withClickEvent(new ClickEvent.ShowDialog(dialogRegistryEntry));
} else {
yield Style.EMPTY;
}
}
};
}

@Override
public ParentTextNode copyWith(TextNode[] children) {
return new ClickActionNode(children, this.action, this.value);
return new ClickActionNode(children, this.action, this.value, this.data);
}

@Override
public ParentTextNode copyWith(TextNode[] children, NodeParser parser) {
return new ClickActionNode(children, this.action, TextNode.asSingle(parser.parseNodes(this.value)));
return new ClickActionNode(children, this.action, TextNode.asSingle(parser.parseNodes(this.value)),
this.data != null && this.data.left().isPresent() ? Either.left(TextNode.asSingle(parser.parseNodes(this.data.left().orElseThrow()))) : this.data);
}

@Override
public boolean isDynamicNoChildren() {
return this.value.isDynamic();
return this.value.isDynamic() || (this.data != null && this.data.left().isEmpty() && this.data.left().orElseThrow().isDynamic());
}

@Override
public String toString() {
return "ClickActionNode{" +
"action=" + action.asString() +
", value=" + value +
", data=" + data +
'}';
}

Expand All @@ -79,6 +162,7 @@ public ClickActionNode(TextNode[] children, Action action, TextNode value) {
super(children);
this.action = action.vanillaType();
this.value = value;
this.data = null;
}

@Deprecated(forRemoval = true)
Expand All @@ -90,6 +174,8 @@ public Action action() {
case RUN_COMMAND -> Action.RUN_COMMAND;
case SUGGEST_COMMAND -> Action.SUGGEST_COMMAND;
case COPY_TO_CLIPBOARD -> Action.COPY_TO_CLIPBOARD;
case SHOW_DIALOG -> Action.SHOW_DIALOG;
case CUSTOM -> Action.CUSTOM;
};
}
@Deprecated(forRemoval = true)
Expand All @@ -100,5 +186,7 @@ public record Action(ClickEvent.Action vanillaType) {
public static final Action RUN_COMMAND = new Action(ClickEvent.Action.RUN_COMMAND);
public static final Action SUGGEST_COMMAND = new Action(ClickEvent.Action.SUGGEST_COMMAND);
public static final Action COPY_TO_CLIPBOARD = new Action(ClickEvent.Action.COPY_TO_CLIPBOARD);
public static final Action SHOW_DIALOG = new Action(ClickEvent.Action.SHOW_DIALOG);
public static final Action CUSTOM = new Action(ClickEvent.Action.CUSTOM);
}
}
62 changes: 62 additions & 0 deletions src/main/java/eu/pb4/placeholders/api/node/parent/StyledNode.java
Original file line number Diff line number Diff line change
@@ -1,14 +1,27 @@
package eu.pb4.placeholders.api.node.parent;

import com.google.gson.JsonParser;
import com.mojang.serialization.JsonOps;
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 eu.pb4.placeholders.impl.GeneralUtils;
import net.minecraft.dialog.type.Dialog;
import net.minecraft.nbt.NbtOps;
import net.minecraft.nbt.StringNbtReader;
import net.minecraft.registry.RegistryKey;
import net.minecraft.registry.RegistryKeys;
import net.minecraft.registry.RegistryWrapper;
import net.minecraft.registry.entry.RegistryEntry;
import net.minecraft.text.ClickEvent;
import net.minecraft.text.HoverEvent;
import net.minecraft.text.Style;
import net.minecraft.util.Identifier;
import org.jetbrains.annotations.Nullable;

import java.net.URI;
import java.util.Optional;

public final class StyledNode extends SimpleStylingNode {
private final Style style;
Expand Down Expand Up @@ -52,6 +65,55 @@ public Style style(ParserContext context) {
case RUN_COMMAND -> style = style.withClickEvent(new ClickEvent.RunCommand(node));
case SUGGEST_COMMAND -> style = style.withClickEvent(new ClickEvent.SuggestCommand(node));
case COPY_TO_CLIPBOARD -> style = style.withClickEvent(new ClickEvent.CopyToClipboard(node));
case CUSTOM -> {
try {
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 {
wrapper = GeneralUtils.DEFAULT_WRAPPER;
}

style = style.withClickEvent(new ClickEvent.Custom(
Identifier.of(node),
/*this.data == null ?*/ Optional.empty() //: Optional.of(StringNbtReader.fromOps(wrapper.getOps(NbtOps.INSTANCE)).read(this.data.toText(context).getString()))
));
} catch (Throwable e) {
// ignore
}

}
case SHOW_DIALOG -> {
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 {
wrapper = GeneralUtils.DEFAULT_WRAPPER;
}
RegistryEntry<Dialog> dialogRegistryEntry = null;

var id = Identifier.tryParse(node);

if (id != null) {
dialogRegistryEntry = wrapper.getOptionalEntry(RegistryKey.of(RegistryKeys.DIALOG, id)).orElse(null);
}

if (dialogRegistryEntry == null) {
try {
dialogRegistryEntry = Dialog.ENTRY_CODEC.decode(
wrapper.getOps(JsonOps.INSTANCE), JsonParser.parseString(node)).getOrThrow().getFirst();
} catch (Throwable e) {
// ignored
}
}
if (dialogRegistryEntry != null) {
style = style.withClickEvent(new ClickEvent.ShowDialog(dialogRegistryEntry));
}
}
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -371,6 +371,9 @@ public interface Format {
if (x != null) {
return x;
}
if (string.charAt(i) == '\\' && maxLength > i + 1) {
i++;
}
}
return null;
}
Expand Down
19 changes: 12 additions & 7 deletions src/main/java/eu/pb4/placeholders/impl/GeneralUtils.java
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,9 @@
import net.fabricmc.loader.api.FabricLoader;
import net.minecraft.component.DataComponentTypes;
import net.minecraft.item.ItemStack;
import net.minecraft.registry.DynamicRegistryManager;
import net.minecraft.registry.Registries;
import net.minecraft.registry.RegistryWrapper;
import net.minecraft.text.*;
import net.minecraft.util.Formatting;
import org.jetbrains.annotations.ApiStatus;
Expand All @@ -22,6 +25,7 @@ public class GeneralUtils {
public static final Logger LOGGER = LoggerFactory.getLogger("Text Placeholder API");
public static final boolean IS_DEV = FabricLoader.getInstance().isDevelopmentEnvironment();
public static final TextNode[] CASTER = new TextNode[0];
public static final RegistryWrapper.WrapperLookup DEFAULT_WRAPPER = DynamicRegistryManager.of(Registries.REGISTRIES);

public static String durationToString(long x) {
long seconds = x % 60;
Expand Down Expand Up @@ -295,13 +299,14 @@ private static StyledNode.HoverData<?> getHoverValue(Style style) {

private static TextNode getClickValue(Style style) {
if (style.getClickEvent() != null) {
return TextNode.of(switch (style.getClickEvent().getAction()) {
case CHANGE_PAGE -> String.valueOf(((ClickEvent.ChangePage) style.getClickEvent()).page());
case COPY_TO_CLIPBOARD -> ((ClickEvent.CopyToClipboard) style.getClickEvent()).value();
case OPEN_FILE -> ((ClickEvent.OpenFile) style.getClickEvent()).file().getPath();
case OPEN_URL -> ((ClickEvent.OpenUrl) style.getClickEvent()).uri().toString();
case RUN_COMMAND -> ((ClickEvent.RunCommand) style.getClickEvent()).command();
case SUGGEST_COMMAND -> ((ClickEvent.SuggestCommand) style.getClickEvent()).command();
return TextNode.of(switch (style.getClickEvent()) {
case ClickEvent.ChangePage event -> String.valueOf(event.page());
case ClickEvent.CopyToClipboard event -> event.value();
case ClickEvent.OpenFile openFile -> openFile.file().getPath();
case ClickEvent.OpenUrl openUrl -> openUrl.uri().toString();
case ClickEvent.RunCommand runCommand -> runCommand.command();
case ClickEvent.SuggestCommand suggestCommand -> suggestCommand.command();
default -> "";
});
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -217,5 +217,25 @@ public static void register() {
}
return PlaceholderResult.invalid("Not enough arguments!");
});

Placeholders.register(Identifier.of("server", "objective_score_player"), (ctx, arg) -> {
var args = arg.split(" ");
if (args.length >= 2) {
ServerScoreboard scoreboard = ctx.server().getScoreboard();
ScoreboardObjective scoreboardObjective = scoreboard.getNullableObjective(args[0]);
if (scoreboardObjective == null) {
return PlaceholderResult.invalid("Invalid Objective!");
}
try {
Collection<ScoreboardEntry> scoreboardEntries = scoreboard.getScoreboardEntries(scoreboardObjective);
ScoreboardEntry entry = scoreboardEntries.stream().filter(scoreboardEntry -> scoreboardEntry.name().getString().equals(args[1])).toList().getFirst();

return PlaceholderResult.value(String.valueOf(entry.value()));
} catch (Exception e) {
return PlaceholderResult.invalid("Player Not Found!");
}
}
return PlaceholderResult.invalid("Not enough arguments!");
});
}
}
Loading