362 lines
14 KiB
Java
362 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.Clearable;
|
|
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, CloneCommands.CommandFunction<CommandContext<CommandSourceStack>, ServerLevel> levelGetter
|
|
) {
|
|
return Commands.argument("begin", BlockPosArgument.blockPos())
|
|
.then(
|
|
Commands.argument("end", BlockPosArgument.blockPos())
|
|
.then(destinationAndModeSuffix(buildContext, levelGetter, commandContext -> commandContext.getSource().getLevel()))
|
|
.then(
|
|
Commands.literal("to")
|
|
.then(
|
|
Commands.argument("targetDimension", DimensionArgument.dimension())
|
|
.then(destinationAndModeSuffix(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, ?> destinationAndModeSuffix(
|
|
CommandBuildContext buildContext,
|
|
CloneCommands.CommandFunction<CommandContext<CommandSourceStack>, ServerLevel> sourceLevelGetter,
|
|
CloneCommands.CommandFunction<CommandContext<CommandSourceStack>, ServerLevel> destinationLevelGetter
|
|
) {
|
|
CloneCommands.CommandFunction<CommandContext<CommandSourceStack>, CloneCommands.DimensionAndPosition> commandFunction = commandContext -> getLoadedDimensionAndPosition(
|
|
commandContext, sourceLevelGetter.apply(commandContext), "begin"
|
|
);
|
|
CloneCommands.CommandFunction<CommandContext<CommandSourceStack>, CloneCommands.DimensionAndPosition> commandFunction2 = commandContext -> getLoadedDimensionAndPosition(
|
|
commandContext, sourceLevelGetter.apply(commandContext), "end"
|
|
);
|
|
CloneCommands.CommandFunction<CommandContext<CommandSourceStack>, CloneCommands.DimensionAndPosition> commandFunction3 = commandContext -> getLoadedDimensionAndPosition(
|
|
commandContext, destinationLevelGetter.apply(commandContext), "destination"
|
|
);
|
|
return Commands.argument("destination", BlockPosArgument.blockPos())
|
|
.executes(
|
|
commandContext -> clone(
|
|
commandContext.getSource(),
|
|
commandFunction.apply(commandContext),
|
|
commandFunction2.apply(commandContext),
|
|
commandFunction3.apply(commandContext),
|
|
blockInWorld -> true,
|
|
CloneCommands.Mode.NORMAL
|
|
)
|
|
)
|
|
.then(
|
|
wrapWithCloneMode(
|
|
commandFunction,
|
|
commandFunction2,
|
|
commandFunction3,
|
|
commandContext -> blockInWorld -> true,
|
|
Commands.literal("replace")
|
|
.executes(
|
|
commandContext -> clone(
|
|
commandContext.getSource(),
|
|
commandFunction.apply(commandContext),
|
|
commandFunction2.apply(commandContext),
|
|
commandFunction3.apply(commandContext),
|
|
blockInWorld -> true,
|
|
CloneCommands.Mode.NORMAL
|
|
)
|
|
)
|
|
)
|
|
)
|
|
.then(
|
|
wrapWithCloneMode(
|
|
commandFunction,
|
|
commandFunction2,
|
|
commandFunction3,
|
|
commandContext -> FILTER_AIR,
|
|
Commands.literal("masked")
|
|
.executes(
|
|
commandContext -> clone(
|
|
commandContext.getSource(),
|
|
commandFunction.apply(commandContext),
|
|
commandFunction2.apply(commandContext),
|
|
commandFunction3.apply(commandContext),
|
|
FILTER_AIR,
|
|
CloneCommands.Mode.NORMAL
|
|
)
|
|
)
|
|
)
|
|
)
|
|
.then(
|
|
Commands.literal("filtered")
|
|
.then(
|
|
wrapWithCloneMode(
|
|
commandFunction,
|
|
commandFunction2,
|
|
commandFunction3,
|
|
commandContext -> BlockPredicateArgument.getBlockPredicate(commandContext, "filter"),
|
|
Commands.argument("filter", BlockPredicateArgument.blockPredicate(buildContext))
|
|
.executes(
|
|
commandContext -> clone(
|
|
commandContext.getSource(),
|
|
commandFunction.apply(commandContext),
|
|
commandFunction2.apply(commandContext),
|
|
commandFunction3.apply(commandContext),
|
|
BlockPredicateArgument.getBlockPredicate(commandContext, "filter"),
|
|
CloneCommands.Mode.NORMAL
|
|
)
|
|
)
|
|
)
|
|
)
|
|
);
|
|
}
|
|
|
|
private static ArgumentBuilder<CommandSourceStack, ?> wrapWithCloneMode(
|
|
CloneCommands.CommandFunction<CommandContext<CommandSourceStack>, CloneCommands.DimensionAndPosition> beginGetter,
|
|
CloneCommands.CommandFunction<CommandContext<CommandSourceStack>, CloneCommands.DimensionAndPosition> endGetter,
|
|
CloneCommands.CommandFunction<CommandContext<CommandSourceStack>, CloneCommands.DimensionAndPosition> targetGetter,
|
|
CloneCommands.CommandFunction<CommandContext<CommandSourceStack>, Predicate<BlockInWorld>> filterGetter,
|
|
ArgumentBuilder<CommandSourceStack, ?> argumentBuilder
|
|
) {
|
|
return argumentBuilder.then(
|
|
Commands.literal("force")
|
|
.executes(
|
|
commandContext -> clone(
|
|
commandContext.getSource(),
|
|
beginGetter.apply(commandContext),
|
|
endGetter.apply(commandContext),
|
|
targetGetter.apply(commandContext),
|
|
filterGetter.apply(commandContext),
|
|
CloneCommands.Mode.FORCE
|
|
)
|
|
)
|
|
)
|
|
.then(
|
|
Commands.literal("move")
|
|
.executes(
|
|
commandContext -> clone(
|
|
commandContext.getSource(),
|
|
beginGetter.apply(commandContext),
|
|
endGetter.apply(commandContext),
|
|
targetGetter.apply(commandContext),
|
|
filterGetter.apply(commandContext),
|
|
CloneCommands.Mode.MOVE
|
|
)
|
|
)
|
|
)
|
|
.then(
|
|
Commands.literal("normal")
|
|
.executes(
|
|
commandContext -> clone(
|
|
commandContext.getSource(),
|
|
beginGetter.apply(commandContext),
|
|
endGetter.apply(commandContext),
|
|
targetGetter.apply(commandContext),
|
|
filterGetter.apply(commandContext),
|
|
CloneCommands.Mode.NORMAL
|
|
)
|
|
)
|
|
);
|
|
}
|
|
|
|
private static int clone(
|
|
CommandSourceStack source,
|
|
CloneCommands.DimensionAndPosition begin,
|
|
CloneCommands.DimensionAndPosition end,
|
|
CloneCommands.DimensionAndPosition target,
|
|
Predicate<BlockInWorld> filter,
|
|
CloneCommands.Mode mode
|
|
) throws CommandSyntaxException {
|
|
BlockPos blockPos = begin.position();
|
|
BlockPos blockPos2 = end.position();
|
|
BoundingBox boundingBox = BoundingBox.fromCorners(blockPos, blockPos2);
|
|
BlockPos blockPos3 = target.position();
|
|
BlockPos blockPos4 = blockPos3.offset(boundingBox.getLength());
|
|
BoundingBox boundingBox2 = BoundingBox.fromCorners(blockPos3, blockPos4);
|
|
ServerLevel serverLevel = begin.dimension();
|
|
ServerLevel serverLevel2 = target.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)) {
|
|
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(serverLevel, blockPos6) && !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);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (mode == CloneCommands.Mode.MOVE) {
|
|
for (BlockPos blockPos8 : deque) {
|
|
BlockEntity blockEntity2 = serverLevel.getBlockEntity(blockPos8);
|
|
Clearable.tryClear(blockEntity2);
|
|
serverLevel.setBlock(blockPos8, Blocks.BARRIER.defaultBlockState(), 2);
|
|
}
|
|
|
|
for (BlockPos blockPos8 : deque) {
|
|
serverLevel.setBlock(blockPos8, Blocks.AIR.defaultBlockState(), 3);
|
|
}
|
|
}
|
|
|
|
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) {
|
|
BlockEntity blockEntity3 = serverLevel2.getBlockEntity(cloneBlockInfo.pos);
|
|
Clearable.tryClear(blockEntity3);
|
|
serverLevel2.setBlock(cloneBlockInfo.pos, Blocks.BARRIER.defaultBlockState(), 2);
|
|
}
|
|
|
|
int mx = 0;
|
|
|
|
for (CloneCommands.CloneBlockInfo cloneBlockInfo2 : list4) {
|
|
if (serverLevel2.setBlock(cloneBlockInfo2.pos, cloneBlockInfo2.state, 2)) {
|
|
mx++;
|
|
}
|
|
}
|
|
|
|
for (CloneCommands.CloneBlockInfo cloneBlockInfo2x : list2) {
|
|
BlockEntity blockEntity4 = serverLevel2.getBlockEntity(cloneBlockInfo2x.pos);
|
|
if (cloneBlockInfo2x.blockEntityInfo != null && blockEntity4 != null) {
|
|
blockEntity4.loadCustomOnly(cloneBlockInfo2x.blockEntityInfo.tag, serverLevel2.registryAccess());
|
|
blockEntity4.setComponents(cloneBlockInfo2x.blockEntityInfo.components);
|
|
blockEntity4.setChanged();
|
|
}
|
|
|
|
serverLevel2.setBlock(cloneBlockInfo2x.pos, cloneBlockInfo2x.state, 2);
|
|
}
|
|
|
|
for (CloneCommands.CloneBlockInfo cloneBlockInfo2x : list5) {
|
|
serverLevel2.blockUpdated(cloneBlockInfo2x.pos, cloneBlockInfo2x.state.getBlock());
|
|
}
|
|
|
|
serverLevel2.getBlockTicks().copyAreaFrom(serverLevel.getBlockTicks(), boundingBox, blockPos5);
|
|
if (mx == 0) {
|
|
throw ERROR_FAILED.create();
|
|
} else {
|
|
int n = mx;
|
|
source.sendSuccess(() -> Component.translatable("commands.clone.success", n), true);
|
|
return mx;
|
|
}
|
|
} else {
|
|
throw BlockPosArgument.ERROR_NOT_LOADED.create();
|
|
}
|
|
}
|
|
}
|
|
|
|
record CloneBlockEntityInfo(CompoundTag tag, DataComponentMap components) {
|
|
}
|
|
|
|
record CloneBlockInfo(BlockPos pos, BlockState state, @Nullable CloneCommands.CloneBlockEntityInfo blockEntityInfo) {
|
|
}
|
|
|
|
@FunctionalInterface
|
|
interface CommandFunction<T, R> {
|
|
R apply(T object) throws CommandSyntaxException;
|
|
}
|
|
|
|
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;
|
|
}
|
|
}
|
|
}
|