minecraft-src/net/minecraft/server/commands/data/DataCommands.java
2025-07-04 03:45:38 +03:00

458 lines
18 KiB
Java

package net.minecraft.server.commands.data;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Iterables;
import com.mojang.brigadier.CommandDispatcher;
import com.mojang.brigadier.arguments.DoubleArgumentType;
import com.mojang.brigadier.arguments.IntegerArgumentType;
import com.mojang.brigadier.builder.ArgumentBuilder;
import com.mojang.brigadier.builder.LiteralArgumentBuilder;
import com.mojang.brigadier.context.CommandContext;
import com.mojang.brigadier.exceptions.CommandSyntaxException;
import com.mojang.brigadier.exceptions.Dynamic2CommandExceptionType;
import com.mojang.brigadier.exceptions.DynamicCommandExceptionType;
import com.mojang.brigadier.exceptions.SimpleCommandExceptionType;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import java.util.function.BiConsumer;
import java.util.function.Function;
import net.minecraft.commands.CommandSourceStack;
import net.minecraft.commands.Commands;
import net.minecraft.commands.arguments.CompoundTagArgument;
import net.minecraft.commands.arguments.NbtPathArgument;
import net.minecraft.commands.arguments.NbtTagArgument;
import net.minecraft.nbt.CollectionTag;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.nbt.EndTag;
import net.minecraft.nbt.NumericTag;
import net.minecraft.nbt.PrimitiveTag;
import net.minecraft.nbt.StringTag;
import net.minecraft.nbt.Tag;
import net.minecraft.network.chat.Component;
import net.minecraft.util.Mth;
public class DataCommands {
private static final SimpleCommandExceptionType ERROR_MERGE_UNCHANGED = new SimpleCommandExceptionType(Component.translatable("commands.data.merge.failed"));
private static final DynamicCommandExceptionType ERROR_GET_NOT_NUMBER = new DynamicCommandExceptionType(
object -> Component.translatableEscape("commands.data.get.invalid", object)
);
private static final DynamicCommandExceptionType ERROR_GET_NON_EXISTENT = new DynamicCommandExceptionType(
object -> Component.translatableEscape("commands.data.get.unknown", object)
);
private static final SimpleCommandExceptionType ERROR_MULTIPLE_TAGS = new SimpleCommandExceptionType(Component.translatable("commands.data.get.multiple"));
private static final DynamicCommandExceptionType ERROR_EXPECTED_OBJECT = new DynamicCommandExceptionType(
object -> Component.translatableEscape("commands.data.modify.expected_object", object)
);
private static final DynamicCommandExceptionType ERROR_EXPECTED_VALUE = new DynamicCommandExceptionType(
object -> Component.translatableEscape("commands.data.modify.expected_value", object)
);
private static final Dynamic2CommandExceptionType ERROR_INVALID_SUBSTRING = new Dynamic2CommandExceptionType(
(object, object2) -> Component.translatableEscape("commands.data.modify.invalid_substring", object, object2)
);
public static final List<Function<String, DataCommands.DataProvider>> ALL_PROVIDERS = ImmutableList.of(
EntityDataAccessor.PROVIDER, BlockDataAccessor.PROVIDER, StorageDataAccessor.PROVIDER
);
public static final List<DataCommands.DataProvider> TARGET_PROVIDERS = (List<DataCommands.DataProvider>)ALL_PROVIDERS.stream()
.map(function -> (DataCommands.DataProvider)function.apply("target"))
.collect(ImmutableList.toImmutableList());
public static final List<DataCommands.DataProvider> SOURCE_PROVIDERS = (List<DataCommands.DataProvider>)ALL_PROVIDERS.stream()
.map(function -> (DataCommands.DataProvider)function.apply("source"))
.collect(ImmutableList.toImmutableList());
public static void register(CommandDispatcher<CommandSourceStack> dispatcher) {
LiteralArgumentBuilder<CommandSourceStack> literalArgumentBuilder = Commands.literal("data")
.requires(commandSourceStack -> commandSourceStack.hasPermission(2));
for (DataCommands.DataProvider dataProvider : TARGET_PROVIDERS) {
literalArgumentBuilder.then(
dataProvider.wrap(
Commands.literal("merge"),
argumentBuilder -> argumentBuilder.then(
Commands.argument("nbt", CompoundTagArgument.compoundTag())
.executes(
commandContext -> mergeData(commandContext.getSource(), dataProvider.access(commandContext), CompoundTagArgument.getCompoundTag(commandContext, "nbt"))
)
)
)
)
.then(
dataProvider.wrap(
Commands.literal("get"),
argumentBuilder -> argumentBuilder.executes(
commandContext -> getData((CommandSourceStack)commandContext.getSource(), dataProvider.access(commandContext))
)
.then(
Commands.argument("path", NbtPathArgument.nbtPath())
.executes(commandContext -> getData(commandContext.getSource(), dataProvider.access(commandContext), NbtPathArgument.getPath(commandContext, "path")))
.then(
Commands.argument("scale", DoubleArgumentType.doubleArg())
.executes(
commandContext -> getNumeric(
commandContext.getSource(),
dataProvider.access(commandContext),
NbtPathArgument.getPath(commandContext, "path"),
DoubleArgumentType.getDouble(commandContext, "scale")
)
)
)
)
)
)
.then(
dataProvider.wrap(
Commands.literal("remove"),
argumentBuilder -> argumentBuilder.then(
Commands.argument("path", NbtPathArgument.nbtPath())
.executes(
commandContext -> removeData(commandContext.getSource(), dataProvider.access(commandContext), NbtPathArgument.getPath(commandContext, "path"))
)
)
)
)
.then(
decorateModification(
(argumentBuilder, dataManipulatorDecorator) -> argumentBuilder.then(
Commands.literal("insert")
.then(
Commands.argument("index", IntegerArgumentType.integer())
.then(
dataManipulatorDecorator.create(
(commandContext, compoundTag, nbtPath, list) -> nbtPath.insert(IntegerArgumentType.getInteger(commandContext, "index"), compoundTag, list)
)
)
)
)
.then(
Commands.literal("prepend").then(dataManipulatorDecorator.create((commandContext, compoundTag, nbtPath, list) -> nbtPath.insert(0, compoundTag, list)))
)
.then(
Commands.literal("append").then(dataManipulatorDecorator.create((commandContext, compoundTag, nbtPath, list) -> nbtPath.insert(-1, compoundTag, list)))
)
.then(
Commands.literal("set")
.then(dataManipulatorDecorator.create((commandContext, compoundTag, nbtPath, list) -> nbtPath.set(compoundTag, Iterables.getLast(list))))
)
.then(Commands.literal("merge").then(dataManipulatorDecorator.create((commandContext, compoundTag, nbtPath, list) -> {
CompoundTag compoundTag2 = new CompoundTag();
for (Tag tag : list) {
if (NbtPathArgument.NbtPath.isTooDeep(tag, 0)) {
throw NbtPathArgument.ERROR_DATA_TOO_DEEP.create();
}
if (!(tag instanceof CompoundTag compoundTag3)) {
throw ERROR_EXPECTED_OBJECT.create(tag);
}
compoundTag2.merge(compoundTag3);
}
Collection<Tag> collection = nbtPath.getOrCreate(compoundTag, CompoundTag::new);
int i = 0;
for (Tag tag2 : collection) {
if (!(tag2 instanceof CompoundTag compoundTag4)) {
throw ERROR_EXPECTED_OBJECT.create(tag2);
}
CompoundTag compoundTag5 = compoundTag4.copy();
compoundTag4.merge(compoundTag2);
i += compoundTag5.equals(compoundTag4) ? 0 : 1;
}
return i;
})))
)
);
}
dispatcher.register(literalArgumentBuilder);
}
private static String getAsText(Tag tag) throws CommandSyntaxException {
return switch (tag) {
case StringTag(String var7) -> var7;
case PrimitiveTag primitiveTag -> primitiveTag.toString();
default -> throw ERROR_EXPECTED_VALUE.create(tag);
};
}
private static List<Tag> stringifyTagList(List<Tag> tagList, DataCommands.StringProcessor processor) throws CommandSyntaxException {
List<Tag> list = new ArrayList(tagList.size());
for (Tag tag : tagList) {
String string = getAsText(tag);
list.add(StringTag.valueOf(processor.process(string)));
}
return list;
}
private static ArgumentBuilder<CommandSourceStack, ?> decorateModification(
BiConsumer<ArgumentBuilder<CommandSourceStack, ?>, DataCommands.DataManipulatorDecorator> decorator
) {
LiteralArgumentBuilder<CommandSourceStack> literalArgumentBuilder = Commands.literal("modify");
for (DataCommands.DataProvider dataProvider : TARGET_PROVIDERS) {
dataProvider.wrap(
literalArgumentBuilder,
argumentBuilder -> {
ArgumentBuilder<CommandSourceStack, ?> argumentBuilder2 = Commands.argument("targetPath", NbtPathArgument.nbtPath());
for (DataCommands.DataProvider dataProvider2 : SOURCE_PROVIDERS) {
decorator.accept(
argumentBuilder2,
(DataCommands.DataManipulatorDecorator)dataManipulator -> dataProvider2.wrap(
Commands.literal("from"),
argumentBuilderx -> argumentBuilderx.executes(
commandContext -> manipulateData(commandContext, dataProvider, dataManipulator, getSingletonSource(commandContext, dataProvider2))
)
.then(
Commands.argument("sourcePath", NbtPathArgument.nbtPath())
.executes(commandContext -> manipulateData(commandContext, dataProvider, dataManipulator, resolveSourcePath(commandContext, dataProvider2)))
)
)
);
decorator.accept(
argumentBuilder2,
(DataCommands.DataManipulatorDecorator)dataManipulator -> dataProvider2.wrap(
Commands.literal("string"),
argumentBuilderx -> argumentBuilderx.executes(
commandContext -> manipulateData(
commandContext, dataProvider, dataManipulator, stringifyTagList(getSingletonSource(commandContext, dataProvider2), string -> string)
)
)
.then(
Commands.argument("sourcePath", NbtPathArgument.nbtPath())
.executes(
commandContext -> manipulateData(
commandContext, dataProvider, dataManipulator, stringifyTagList(resolveSourcePath(commandContext, dataProvider2), string -> string)
)
)
.then(
Commands.argument("start", IntegerArgumentType.integer())
.executes(
commandContext -> manipulateData(
commandContext,
dataProvider,
dataManipulator,
stringifyTagList(
resolveSourcePath(commandContext, dataProvider2), string -> substring(string, IntegerArgumentType.getInteger(commandContext, "start"))
)
)
)
.then(
Commands.argument("end", IntegerArgumentType.integer())
.executes(
commandContext -> manipulateData(
commandContext,
dataProvider,
dataManipulator,
stringifyTagList(
resolveSourcePath(commandContext, dataProvider2),
string -> substring(string, IntegerArgumentType.getInteger(commandContext, "start"), IntegerArgumentType.getInteger(commandContext, "end"))
)
)
)
)
)
)
)
);
}
decorator.accept(
argumentBuilder2,
(DataCommands.DataManipulatorDecorator)dataManipulator -> Commands.literal("value")
.then(Commands.argument("value", NbtTagArgument.nbtTag()).executes(commandContext -> {
List<Tag> list = Collections.singletonList(NbtTagArgument.getNbtTag(commandContext, "value"));
return manipulateData(commandContext, dataProvider, dataManipulator, list);
}))
);
return argumentBuilder.then(argumentBuilder2);
}
);
}
return literalArgumentBuilder;
}
private static String validatedSubstring(String source, int start, int end) throws CommandSyntaxException {
if (start >= 0 && end <= source.length() && start <= end) {
return source.substring(start, end);
} else {
throw ERROR_INVALID_SUBSTRING.create(start, end);
}
}
private static String substring(String source, int start, int end) throws CommandSyntaxException {
int i = source.length();
int j = getOffset(start, i);
int k = getOffset(end, i);
return validatedSubstring(source, j, k);
}
private static String substring(String source, int start) throws CommandSyntaxException {
int i = source.length();
return validatedSubstring(source, getOffset(start, i), i);
}
private static int getOffset(int index, int length) {
return index >= 0 ? index : length + index;
}
private static List<Tag> getSingletonSource(CommandContext<CommandSourceStack> context, DataCommands.DataProvider dataProvider) throws CommandSyntaxException {
DataAccessor dataAccessor = dataProvider.access(context);
return Collections.singletonList(dataAccessor.getData());
}
private static List<Tag> resolveSourcePath(CommandContext<CommandSourceStack> context, DataCommands.DataProvider dataProvider) throws CommandSyntaxException {
DataAccessor dataAccessor = dataProvider.access(context);
NbtPathArgument.NbtPath nbtPath = NbtPathArgument.getPath(context, "sourcePath");
return nbtPath.get(dataAccessor.getData());
}
private static int manipulateData(
CommandContext<CommandSourceStack> source, DataCommands.DataProvider dataProvider, DataCommands.DataManipulator dataManipulator, List<Tag> tags
) throws CommandSyntaxException {
DataAccessor dataAccessor = dataProvider.access(source);
NbtPathArgument.NbtPath nbtPath = NbtPathArgument.getPath(source, "targetPath");
CompoundTag compoundTag = dataAccessor.getData();
int i = dataManipulator.modify(source, compoundTag, nbtPath, tags);
if (i == 0) {
throw ERROR_MERGE_UNCHANGED.create();
} else {
dataAccessor.setData(compoundTag);
source.getSource().sendSuccess(() -> dataAccessor.getModifiedSuccess(), true);
return i;
}
}
/**
* Removes the tag at the end of the path.
*
* @return 1
*/
private static int removeData(CommandSourceStack source, DataAccessor accessor, NbtPathArgument.NbtPath path) throws CommandSyntaxException {
CompoundTag compoundTag = accessor.getData();
int i = path.remove(compoundTag);
if (i == 0) {
throw ERROR_MERGE_UNCHANGED.create();
} else {
accessor.setData(compoundTag);
source.sendSuccess(() -> accessor.getModifiedSuccess(), true);
return i;
}
}
public static Tag getSingleTag(NbtPathArgument.NbtPath path, DataAccessor accessor) throws CommandSyntaxException {
Collection<Tag> collection = path.get(accessor.getData());
Iterator<Tag> iterator = collection.iterator();
Tag tag = (Tag)iterator.next();
if (iterator.hasNext()) {
throw ERROR_MULTIPLE_TAGS.create();
} else {
return tag;
}
}
/**
* Gets a value, which can be of any known NBT type.
*
* @return The value associated with the element: length for strings, size for lists and compounds, and numeric value for primitives.
*/
private static int getData(CommandSourceStack source, DataAccessor accessor, NbtPathArgument.NbtPath path) throws CommandSyntaxException {
Tag tag = getSingleTag(path, accessor);
int i = switch (tag) {
case NumericTag numericTag -> Mth.floor(numericTag.doubleValue());
case CollectionTag collectionTag -> collectionTag.size();
case CompoundTag compoundTag -> compoundTag.size();
case StringTag(String var14) -> var14.length();
case EndTag endTag -> throw ERROR_GET_NON_EXISTENT.create(path.toString());
default -> throw new MatchException(null, null);
};
source.sendSuccess(() -> accessor.getPrintSuccess(tag), false);
return i;
}
/**
* Gets a single numeric element, scaled by the given amount.
*
* @return The element's value, scaled by scale.
*/
private static int getNumeric(CommandSourceStack source, DataAccessor accessor, NbtPathArgument.NbtPath path, double scale) throws CommandSyntaxException {
Tag tag = getSingleTag(path, accessor);
if (!(tag instanceof NumericTag)) {
throw ERROR_GET_NOT_NUMBER.create(path.toString());
} else {
int i = Mth.floor(((NumericTag)tag).doubleValue() * scale);
source.sendSuccess(() -> accessor.getPrintSuccess(path, scale, i), false);
return i;
}
}
/**
* Gets all NBT on the object, and applies syntax highlighting.
*
* @return 1
*/
private static int getData(CommandSourceStack source, DataAccessor accessor) throws CommandSyntaxException {
CompoundTag compoundTag = accessor.getData();
source.sendSuccess(() -> accessor.getPrintSuccess(compoundTag), false);
return 1;
}
/**
* Merges the given NBT into the targeted object's NBT.
*
* @return 1
*/
private static int mergeData(CommandSourceStack source, DataAccessor accessor, CompoundTag nbt) throws CommandSyntaxException {
CompoundTag compoundTag = accessor.getData();
if (NbtPathArgument.NbtPath.isTooDeep(nbt, 0)) {
throw NbtPathArgument.ERROR_DATA_TOO_DEEP.create();
} else {
CompoundTag compoundTag2 = compoundTag.copy().merge(nbt);
if (compoundTag.equals(compoundTag2)) {
throw ERROR_MERGE_UNCHANGED.create();
} else {
accessor.setData(compoundTag2);
source.sendSuccess(() -> accessor.getModifiedSuccess(), true);
return 1;
}
}
}
@FunctionalInterface
interface DataManipulator {
int modify(CommandContext<CommandSourceStack> commandContext, CompoundTag compoundTag, NbtPathArgument.NbtPath nbtPath, List<Tag> list) throws CommandSyntaxException;
}
@FunctionalInterface
interface DataManipulatorDecorator {
ArgumentBuilder<CommandSourceStack, ?> create(DataCommands.DataManipulator dataManipulator);
}
public interface DataProvider {
/**
* Creates an accessor based on the command context. This should only refer to arguments registered in {@link createArgument}.
*/
DataAccessor access(CommandContext<CommandSourceStack> context) throws CommandSyntaxException;
/**
* Creates an argument used for accessing data related to this type of thing, including a literal to distinguish from other types.
*/
ArgumentBuilder<CommandSourceStack, ?> wrap(
ArgumentBuilder<CommandSourceStack, ?> builder, Function<ArgumentBuilder<CommandSourceStack, ?>, ArgumentBuilder<CommandSourceStack, ?>> action
);
}
@FunctionalInterface
interface StringProcessor {
String process(String string) throws CommandSyntaxException;
}
}