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

346 lines
14 KiB
Java

package net.minecraft.server.commands;
import com.google.common.collect.Lists;
import com.mojang.brigadier.CommandDispatcher;
import com.mojang.brigadier.builder.ArgumentBuilder;
import com.mojang.brigadier.context.CommandContext;
import com.mojang.brigadier.exceptions.CommandSyntaxException;
import com.mojang.brigadier.exceptions.Dynamic2CommandExceptionType;
import com.mojang.brigadier.exceptions.SimpleCommandExceptionType;
import java.util.Deque;
import java.util.List;
import java.util.function.Predicate;
import net.minecraft.commands.CommandBuildContext;
import net.minecraft.commands.CommandSourceStack;
import net.minecraft.commands.Commands;
import net.minecraft.commands.arguments.DimensionArgument;
import net.minecraft.commands.arguments.blocks.BlockPredicateArgument;
import net.minecraft.commands.arguments.coordinates.BlockPosArgument;
import net.minecraft.core.BlockPos;
import net.minecraft.core.component.DataComponentMap;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.network.chat.Component;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.world.level.GameRules;
import net.minecraft.world.level.block.Blocks;
import net.minecraft.world.level.block.entity.BlockEntity;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.level.block.state.pattern.BlockInWorld;
import net.minecraft.world.level.levelgen.structure.BoundingBox;
import org.jetbrains.annotations.Nullable;
public class CloneCommands {
private static final SimpleCommandExceptionType ERROR_OVERLAP = new SimpleCommandExceptionType(Component.translatable("commands.clone.overlap"));
private static final Dynamic2CommandExceptionType ERROR_AREA_TOO_LARGE = new Dynamic2CommandExceptionType(
(object, object2) -> Component.translatableEscape("commands.clone.toobig", object, object2)
);
private static final SimpleCommandExceptionType ERROR_FAILED = new SimpleCommandExceptionType(Component.translatable("commands.clone.failed"));
public static final Predicate<BlockInWorld> FILTER_AIR = blockInWorld -> !blockInWorld.getState().isAir();
public static void register(CommandDispatcher<CommandSourceStack> dispatcher, CommandBuildContext context) {
dispatcher.register(
Commands.literal("clone")
.requires(commandSourceStack -> commandSourceStack.hasPermission(2))
.then(beginEndDestinationAndModeSuffix(context, commandContext -> commandContext.getSource().getLevel()))
.then(
Commands.literal("from")
.then(
Commands.argument("sourceDimension", DimensionArgument.dimension())
.then(beginEndDestinationAndModeSuffix(context, commandContext -> DimensionArgument.getDimension(commandContext, "sourceDimension")))
)
)
);
}
private static ArgumentBuilder<CommandSourceStack, ?> beginEndDestinationAndModeSuffix(
CommandBuildContext buildContext, InCommandFunction<CommandContext<CommandSourceStack>, ServerLevel> levelGetter
) {
return Commands.argument("begin", BlockPosArgument.blockPos())
.then(
Commands.argument("end", BlockPosArgument.blockPos())
.then(destinationAndStrictSuffix(buildContext, levelGetter, commandContext -> commandContext.getSource().getLevel()))
.then(
Commands.literal("to")
.then(
Commands.argument("targetDimension", DimensionArgument.dimension())
.then(destinationAndStrictSuffix(buildContext, levelGetter, commandContext -> DimensionArgument.getDimension(commandContext, "targetDimension")))
)
)
);
}
private static CloneCommands.DimensionAndPosition getLoadedDimensionAndPosition(CommandContext<CommandSourceStack> context, ServerLevel level, String name) throws CommandSyntaxException {
BlockPos blockPos = BlockPosArgument.getLoadedBlockPos(context, level, name);
return new CloneCommands.DimensionAndPosition(level, blockPos);
}
private static ArgumentBuilder<CommandSourceStack, ?> destinationAndStrictSuffix(
CommandBuildContext buildContext,
InCommandFunction<CommandContext<CommandSourceStack>, ServerLevel> sourceLevelGetter,
InCommandFunction<CommandContext<CommandSourceStack>, ServerLevel> destinationLevelGetter
) {
InCommandFunction<CommandContext<CommandSourceStack>, CloneCommands.DimensionAndPosition> inCommandFunction = commandContext -> getLoadedDimensionAndPosition(
commandContext, sourceLevelGetter.apply(commandContext), "begin"
);
InCommandFunction<CommandContext<CommandSourceStack>, CloneCommands.DimensionAndPosition> inCommandFunction2 = commandContext -> getLoadedDimensionAndPosition(
commandContext, sourceLevelGetter.apply(commandContext), "end"
);
InCommandFunction<CommandContext<CommandSourceStack>, CloneCommands.DimensionAndPosition> inCommandFunction3 = commandContext -> getLoadedDimensionAndPosition(
commandContext, destinationLevelGetter.apply(commandContext), "destination"
);
return modeSuffix(
buildContext, inCommandFunction, inCommandFunction2, inCommandFunction3, false, Commands.argument("destination", BlockPosArgument.blockPos())
)
.then(modeSuffix(buildContext, inCommandFunction, inCommandFunction2, inCommandFunction3, true, Commands.literal("strict")));
}
private static ArgumentBuilder<CommandSourceStack, ?> modeSuffix(
CommandBuildContext buildContext,
InCommandFunction<CommandContext<CommandSourceStack>, CloneCommands.DimensionAndPosition> begin,
InCommandFunction<CommandContext<CommandSourceStack>, CloneCommands.DimensionAndPosition> end,
InCommandFunction<CommandContext<CommandSourceStack>, CloneCommands.DimensionAndPosition> destination,
boolean strict,
ArgumentBuilder<CommandSourceStack, ?> argumentBuilder
) {
return argumentBuilder.executes(
commandContext -> clone(
commandContext.getSource(),
begin.apply(commandContext),
end.apply(commandContext),
destination.apply(commandContext),
blockInWorld -> true,
CloneCommands.Mode.NORMAL,
strict
)
)
.then(wrapWithCloneMode(begin, end, destination, commandContext -> blockInWorld -> true, strict, Commands.literal("replace")))
.then(wrapWithCloneMode(begin, end, destination, commandContext -> FILTER_AIR, strict, Commands.literal("masked")))
.then(
Commands.literal("filtered")
.then(
wrapWithCloneMode(
begin,
end,
destination,
commandContext -> BlockPredicateArgument.getBlockPredicate(commandContext, "filter"),
strict,
Commands.argument("filter", BlockPredicateArgument.blockPredicate(buildContext))
)
)
);
}
private static ArgumentBuilder<CommandSourceStack, ?> wrapWithCloneMode(
InCommandFunction<CommandContext<CommandSourceStack>, CloneCommands.DimensionAndPosition> begin,
InCommandFunction<CommandContext<CommandSourceStack>, CloneCommands.DimensionAndPosition> end,
InCommandFunction<CommandContext<CommandSourceStack>, CloneCommands.DimensionAndPosition> destination,
InCommandFunction<CommandContext<CommandSourceStack>, Predicate<BlockInWorld>> filter,
boolean strict,
ArgumentBuilder<CommandSourceStack, ?> argumentBuilder
) {
return argumentBuilder.executes(
commandContext -> clone(
commandContext.getSource(),
begin.apply(commandContext),
end.apply(commandContext),
destination.apply(commandContext),
filter.apply(commandContext),
CloneCommands.Mode.NORMAL,
strict
)
)
.then(
Commands.literal("force")
.executes(
commandContext -> clone(
commandContext.getSource(),
begin.apply(commandContext),
end.apply(commandContext),
destination.apply(commandContext),
filter.apply(commandContext),
CloneCommands.Mode.FORCE,
strict
)
)
)
.then(
Commands.literal("move")
.executes(
commandContext -> clone(
commandContext.getSource(),
begin.apply(commandContext),
end.apply(commandContext),
destination.apply(commandContext),
filter.apply(commandContext),
CloneCommands.Mode.MOVE,
strict
)
)
)
.then(
Commands.literal("normal")
.executes(
commandContext -> clone(
commandContext.getSource(),
begin.apply(commandContext),
end.apply(commandContext),
destination.apply(commandContext),
filter.apply(commandContext),
CloneCommands.Mode.NORMAL,
strict
)
)
);
}
private static int clone(
CommandSourceStack source,
CloneCommands.DimensionAndPosition begin,
CloneCommands.DimensionAndPosition end,
CloneCommands.DimensionAndPosition destination,
Predicate<BlockInWorld> filter,
CloneCommands.Mode mode,
boolean strict
) throws CommandSyntaxException {
BlockPos blockPos = begin.position();
BlockPos blockPos2 = end.position();
BoundingBox boundingBox = BoundingBox.fromCorners(blockPos, blockPos2);
BlockPos blockPos3 = destination.position();
BlockPos blockPos4 = blockPos3.offset(boundingBox.getLength());
BoundingBox boundingBox2 = BoundingBox.fromCorners(blockPos3, blockPos4);
ServerLevel serverLevel = begin.dimension();
ServerLevel serverLevel2 = destination.dimension();
if (!mode.canOverlap() && serverLevel == serverLevel2 && boundingBox2.intersects(boundingBox)) {
throw ERROR_OVERLAP.create();
} else {
int i = boundingBox.getXSpan() * boundingBox.getYSpan() * boundingBox.getZSpan();
int j = source.getLevel().getGameRules().getInt(GameRules.RULE_COMMAND_MODIFICATION_BLOCK_LIMIT);
if (i > j) {
throw ERROR_AREA_TOO_LARGE.create(j, i);
} else if (serverLevel.hasChunksAt(blockPos, blockPos2) && serverLevel2.hasChunksAt(blockPos3, blockPos4)) {
if (serverLevel2.isDebug()) {
throw ERROR_FAILED.create();
} else {
List<CloneCommands.CloneBlockInfo> list = Lists.<CloneCommands.CloneBlockInfo>newArrayList();
List<CloneCommands.CloneBlockInfo> list2 = Lists.<CloneCommands.CloneBlockInfo>newArrayList();
List<CloneCommands.CloneBlockInfo> list3 = Lists.<CloneCommands.CloneBlockInfo>newArrayList();
Deque<BlockPos> deque = Lists.<BlockPos>newLinkedList();
BlockPos blockPos5 = new BlockPos(
boundingBox2.minX() - boundingBox.minX(), boundingBox2.minY() - boundingBox.minY(), boundingBox2.minZ() - boundingBox.minZ()
);
for (int k = boundingBox.minZ(); k <= boundingBox.maxZ(); k++) {
for (int l = boundingBox.minY(); l <= boundingBox.maxY(); l++) {
for (int m = boundingBox.minX(); m <= boundingBox.maxX(); m++) {
BlockPos blockPos6 = new BlockPos(m, l, k);
BlockPos blockPos7 = blockPos6.offset(blockPos5);
BlockInWorld blockInWorld = new BlockInWorld(serverLevel, blockPos6, false);
BlockState blockState = blockInWorld.getState();
if (filter.test(blockInWorld)) {
BlockEntity blockEntity = serverLevel.getBlockEntity(blockPos6);
if (blockEntity != null) {
CloneCommands.CloneBlockEntityInfo cloneBlockEntityInfo = new CloneCommands.CloneBlockEntityInfo(
blockEntity.saveCustomOnly(source.registryAccess()), blockEntity.components()
);
list2.add(new CloneCommands.CloneBlockInfo(blockPos7, blockState, cloneBlockEntityInfo));
deque.addLast(blockPos6);
} else if (!blockState.isSolidRender() && !blockState.isCollisionShapeFullBlock(serverLevel, blockPos6)) {
list3.add(new CloneCommands.CloneBlockInfo(blockPos7, blockState, null));
deque.addFirst(blockPos6);
} else {
list.add(new CloneCommands.CloneBlockInfo(blockPos7, blockState, null));
deque.addLast(blockPos6);
}
}
}
}
}
int k = 2 | (strict ? 816 : 0);
if (mode == CloneCommands.Mode.MOVE) {
for (BlockPos blockPos8 : deque) {
serverLevel.setBlock(blockPos8, Blocks.BARRIER.defaultBlockState(), k | 816);
}
int l = strict ? k : 3;
for (BlockPos blockPos6 : deque) {
serverLevel.setBlock(blockPos6, Blocks.AIR.defaultBlockState(), l);
}
}
List<CloneCommands.CloneBlockInfo> list4 = Lists.<CloneCommands.CloneBlockInfo>newArrayList();
list4.addAll(list);
list4.addAll(list2);
list4.addAll(list3);
List<CloneCommands.CloneBlockInfo> list5 = Lists.reverse(list4);
for (CloneCommands.CloneBlockInfo cloneBlockInfo : list5) {
serverLevel2.setBlock(cloneBlockInfo.pos, Blocks.BARRIER.defaultBlockState(), k | 816);
}
int n = 0;
for (CloneCommands.CloneBlockInfo cloneBlockInfo2 : list4) {
if (serverLevel2.setBlock(cloneBlockInfo2.pos, cloneBlockInfo2.state, k)) {
n++;
}
}
for (CloneCommands.CloneBlockInfo cloneBlockInfo2x : list2) {
BlockEntity blockEntity2 = serverLevel2.getBlockEntity(cloneBlockInfo2x.pos);
if (cloneBlockInfo2x.blockEntityInfo != null && blockEntity2 != null) {
blockEntity2.loadCustomOnly(cloneBlockInfo2x.blockEntityInfo.tag, serverLevel2.registryAccess());
blockEntity2.setComponents(cloneBlockInfo2x.blockEntityInfo.components);
blockEntity2.setChanged();
}
serverLevel2.setBlock(cloneBlockInfo2x.pos, cloneBlockInfo2x.state, k);
}
if (!strict) {
for (CloneCommands.CloneBlockInfo cloneBlockInfo2x : list5) {
serverLevel2.updateNeighborsAt(cloneBlockInfo2x.pos, cloneBlockInfo2x.state.getBlock());
}
}
serverLevel2.getBlockTicks().copyAreaFrom(serverLevel.getBlockTicks(), boundingBox, blockPos5);
if (n == 0) {
throw ERROR_FAILED.create();
} else {
int o = n;
source.sendSuccess(() -> Component.translatable("commands.clone.success", o), true);
return n;
}
}
} else {
throw BlockPosArgument.ERROR_NOT_LOADED.create();
}
}
}
record CloneBlockEntityInfo(CompoundTag tag, DataComponentMap components) {
}
record CloneBlockInfo(BlockPos pos, BlockState state, @Nullable CloneCommands.CloneBlockEntityInfo blockEntityInfo) {
}
record DimensionAndPosition(ServerLevel dimension, BlockPos position) {
}
static enum Mode {
FORCE(true),
MOVE(true),
NORMAL(false);
private final boolean canOverlap;
private Mode(final boolean canOverlap) {
this.canOverlap = canOverlap;
}
public boolean canOverlap() {
return this.canOverlap;
}
}
}