blob: ac49ba26595ae4cc29c6cf99119bcdd4a080fcc8 [file] [log] [blame] [raw]
package net.glowstone.command.minecraft;
import com.google.common.collect.AbstractIterator;
import java.util.Collections;
import java.util.Iterator;
import net.glowstone.GlowWorld;
import net.glowstone.block.GlowBlock;
import net.glowstone.block.entity.BlockEntity;
import net.glowstone.command.CommandUtils;
import net.glowstone.constants.ItemIds;
import net.glowstone.util.nbt.CompoundTag;
import org.bukkit.ChatColor;
import org.bukkit.Location;
import org.bukkit.Material;
import org.bukkit.command.CommandSender;
import org.bukkit.command.defaults.VanillaCommand;
import org.jetbrains.annotations.NotNull;
public class CloneCommand extends VanillaCommand {
public enum MaskMode {
REPLACE("replace"),
MASKED("masked"),
FILTERED("filter");
private final String commandName;
MaskMode(String commandName) {
this.commandName = commandName;
}
public String getCommandName() {
return commandName;
}
public static MaskMode fromCommandName(String commandName) {
for (MaskMode maskMode : values()) {
if (maskMode.getCommandName().equals(commandName)) {
return maskMode;
}
}
return null;
}
}
public enum CloneMode {
NORMAL("normal", false),
FORCE("force", true),
MOVE("move", false);
private final String commandName;
private final boolean allowedToOverlap;
CloneMode(String commandName, boolean allowedToOverlap) {
this.commandName = commandName;
this.allowedToOverlap = allowedToOverlap;
}
public String getCommandName() {
return commandName;
}
public boolean isAllowedToOverlap() {
return allowedToOverlap;
}
public static CloneMode fromCommandName(String commandName) {
for (CloneMode cloneMode : values()) {
if (cloneMode.getCommandName().equals(commandName)) {
return cloneMode;
}
}
return null;
}
}
public CloneCommand() {
super(
"clone",
"Clones a section of the world.", "/clone <x1> <y1> <z1> <x2> <y2> <z2> <x> <y> <z> [maskMode] [cloneMode] [tileName] [tileData]",
Collections.emptyList()
);
setPermission("minecraft.command.clone");
}
@Override
public boolean execute(CommandSender sender, String label, String[] args) {
if (!testPermission(sender)) {
return false;
}
if (args.length < 9) {
sender.sendMessage(ChatColor.RED + "Usage: " + usageMessage);
return false;
}
if (!CommandUtils.isPhysical(sender)) {
sender.sendMessage("This command may only be executed by physical objects");
return false;
}
GlowWorld world = CommandUtils.getWorld(sender);
Location parsedFrom1 = CommandUtils.getLocation(sender, args[0], args[1], args[2]);
Location parsedFrom2 = CommandUtils.getLocation(sender, args[3], args[4], args[5]);
Location to = CommandUtils.getLocation(sender, args[6], args[7], args[8]);
Location lowCorner = new Location(
world,
Double.min(parsedFrom1.getX(), parsedFrom2.getX()),
Double.min(parsedFrom1.getY(), parsedFrom2.getY()),
Double.min(parsedFrom1.getZ(), parsedFrom2.getZ())
);
Location highCorner = new Location(
world,
Double.max(parsedFrom1.getX(), parsedFrom2.getX()),
Double.max(parsedFrom1.getY(), parsedFrom2.getY()),
Double.max(parsedFrom1.getZ(), parsedFrom2.getZ())
);
MaskMode maskMode = args.length >= 10 ? MaskMode.fromCommandName(args[9]) : MaskMode.REPLACE;
CloneMode cloneMode = args.length >= 11 ? CloneMode.fromCommandName(args[10]) : CloneMode.NORMAL;
// TODO: Investigate what happens when maskMode or cloneMode are invalid (thus, null).
if (maskMode == null || cloneMode == null) {
sender.sendMessage(ChatColor.RED + "Usage: " + usageMessage);
return false;
}
BlockFilter blockFilter;
switch (maskMode) {
case REPLACE:
blockFilter = new ReplaceBlockFilter();
break;
case MASKED:
blockFilter = new MaskedBlockFilter();
break;
case FILTERED:
if (args.length >= 12) {
Material blockType = ItemIds.getItem(args[11]);
if (args.length >= 13) {
Byte data;
try {
data = Byte.parseByte(args[12]);
} catch (NumberFormatException ignored) {
data = null;
}
if (data == null || 0 > data || data > 15) {
sender.sendMessage(ChatColor.RED + "Filtered block data not a number between 0 and 15, inclusive.");
return false;
} else {
blockFilter = new FilteredWithDataBlockFilter(blockType, data);
}
} else {
blockFilter = new FilteredBlockFilter(blockType);
}
} else {
sender.sendMessage(ChatColor.RED + "You must specify a block type and, optionally, block data when using the filtered mask mode.");
return false;
}
break;
default:
sender.sendMessage(ChatColor.RED + "Usage: " + usageMessage);
return false;
}
boolean overlaps = between(lowCorner.getBlockX(), highCorner.getBlockX(), to.getBlockX()) ||
between(lowCorner.getBlockY(), highCorner.getBlockY(), to.getBlockY()) ||
between(lowCorner.getBlockZ(), highCorner.getBlockZ(), to.getBlockZ());
if (overlaps && !cloneMode.isAllowedToOverlap()) {
sender.sendMessage(ChatColor.RED + "Usage: " + usageMessage);
return false;
}
int blocksCloned = 0;
int widthX = highCorner.getBlockX() - lowCorner.getBlockX() + 1;
int widthY = highCorner.getBlockY() - lowCorner.getBlockY() + 1;
int widthZ = highCorner.getBlockZ() - lowCorner.getBlockZ() + 1;
Iterable<Integer> xIter = to.getBlockX() < lowCorner.getBlockX() ? new ForwardsAxisIterable(widthX) : new BackwardsAxisIterable(widthX);
Iterable<Integer> yIter = to.getBlockY() < lowCorner.getBlockY() ? new ForwardsAxisIterable(widthY) : new BackwardsAxisIterable(widthY);
Iterable<Integer> zIter = to.getBlockZ() < lowCorner.getBlockZ() ? new ForwardsAxisIterable(widthZ) : new BackwardsAxisIterable(widthZ);
for (int x : xIter) {
for (int y : yIter) {
for (int z : zIter) {
GlowBlock fromBlock = world.getBlockAt(lowCorner.getBlockX() + x, lowCorner.getBlockY() + y, lowCorner.getBlockZ() + z);
if (blockFilter.shouldClone(fromBlock)) {
GlowBlock toBlock = world.getBlockAt(to.getBlockX() + x, to.getBlockY() + y, to.getBlockZ() + z);
toBlock.setTypeIdAndData(fromBlock.getTypeId(), fromBlock.getData(), false);
BlockEntity fromEntity = fromBlock.getBlockEntity();
if (fromEntity != null) {
BlockEntity toEntity = toBlock.getChunk().createEntity(toBlock.getX(), toBlock.getY(), toBlock.getZ(), toBlock.getTypeId());
if (toEntity != null) {
CompoundTag entityTag = new CompoundTag();
fromEntity.saveNbt(entityTag);
toEntity.loadNbt(entityTag);
}
}
if (cloneMode == CloneMode.MOVE) {
fromBlock.setType(Material.AIR, false);
}
blocksCloned++;
}
}
}
}
if (blocksCloned == 0) {
sender.sendMessage(ChatColor.RED + "No blocks cloned.");
} else {
sender.sendMessage("Cloned " + blocksCloned + " blocks.");
}
return true;
}
private static boolean between(double low, double high, double n) {
return low < n && n < high;
}
private static class ForwardsAxisIterable implements Iterable<Integer> {
private final int max;
private ForwardsAxisIterable(int max) {
this.max = max;
}
@NotNull
@Override
public Iterator<Integer> iterator() {
return new ForwardsAxisIterator(max);
}
}
private static class ForwardsAxisIterator extends AbstractIterator<Integer> {
private final int max;
private int current;
public ForwardsAxisIterator(int max) {
this.max = max;
this.current = 0;
}
@Override
protected Integer computeNext() {
if (current <= max) {
// Could use a post-increment operator here, but this is a bit more clear in getting the meaning across.
int retval = current;
current++;
return retval;
}
return endOfData();
}
}
private static class BackwardsAxisIterable implements Iterable<Integer> {
private final int max;
private BackwardsAxisIterable(int max) {
this.max = max;
}
@NotNull
@Override
public Iterator<Integer> iterator() {
return new BackwardsAxisIterator(max);
}
}
private static class BackwardsAxisIterator extends AbstractIterator<Integer> {
private int current;
public BackwardsAxisIterator(int length) {
this.current = length;
}
@Override
protected Integer computeNext() {
if (current >= 0) {
// Could use a post-decrement operator here, but this is a bit more clear in getting the meaning across.
int retval = current;
current--;
return retval;
}
return endOfData();
}
}
private interface BlockFilter {
boolean shouldClone(GlowBlock block);
}
private static class ReplaceBlockFilter implements BlockFilter {
@Override
public boolean shouldClone(GlowBlock block) {
return true;
}
}
private static class MaskedBlockFilter implements BlockFilter {
@Override
public boolean shouldClone(GlowBlock block) {
return block.getType() != Material.AIR;
}
}
private static class FilteredBlockFilter implements BlockFilter {
private final Material blockType;
private FilteredBlockFilter(Material blockType) {
this.blockType = blockType;
}
@Override
public boolean shouldClone(GlowBlock block) {
return block.getType() == blockType;
}
}
private static class FilteredWithDataBlockFilter extends FilteredBlockFilter {
private final byte data;
private FilteredWithDataBlockFilter(Material blockType, byte data) {
super(blockType);
this.data = data;
}
@Override
public boolean shouldClone(GlowBlock block) {
return super.shouldClone(block) && block.getData() == data;
}
}
}