bentobox/src/main/java/world/bentobox/bentobox/schems/Clipboard.java

573 lines
22 KiB
Java

package world.bentobox.bentobox.schems;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;
import java.util.Objects;
import java.util.stream.Collectors;
import java.util.zip.ZipEntry;
import java.util.zip.ZipInputStream;
import java.util.zip.ZipOutputStream;
import org.bukkit.Bukkit;
import org.bukkit.DyeColor;
import org.bukkit.Location;
import org.bukkit.Material;
import org.bukkit.World;
import org.bukkit.block.Block;
import org.bukkit.block.BlockState;
import org.bukkit.block.CreatureSpawner;
import org.bukkit.block.Sign;
import org.bukkit.configuration.ConfigurationSection;
import org.bukkit.configuration.InvalidConfigurationException;
import org.bukkit.configuration.file.YamlConfiguration;
import org.bukkit.entity.AbstractHorse;
import org.bukkit.entity.Ageable;
import org.bukkit.entity.ChestedHorse;
import org.bukkit.entity.EntityType;
import org.bukkit.entity.Horse;
import org.bukkit.entity.LivingEntity;
import org.bukkit.entity.Player;
import org.bukkit.entity.Tameable;
import org.bukkit.inventory.Inventory;
import org.bukkit.inventory.InventoryHolder;
import org.bukkit.inventory.ItemStack;
import org.bukkit.material.Colorable;
import org.bukkit.util.Vector;
import world.bentobox.bentobox.BentoBox;
import world.bentobox.bentobox.api.localization.TextVariables;
import world.bentobox.bentobox.api.user.User;
import world.bentobox.bentobox.database.objects.Island;
import world.bentobox.bentobox.util.Util;
/**
* @author tastybento
*/
public class Clipboard {
// Commonly used texts along this class.
private static final String ATTACHED = "attached";
private static final String BLOCK = "blocks";
private static final String BEDROCK = "bedrock";
private static final String INVENTORY = "inventory";
private static final String ENTITY = "entity";
private static final String COLOR = "color";
private static final String LOAD_ERROR = "Could not load schems file - does not exist : ";
private YamlConfiguration blockConfig = new YamlConfiguration();
private Location pos1;
private Location pos2;
private Location origin;
private BentoBox plugin;
private boolean copied;
private File schemFolder;
public Clipboard(BentoBox plugin, File schemFolder) {
super();
this.plugin = plugin;
if (!schemFolder.exists()) {
schemFolder.mkdirs();
}
this.schemFolder = schemFolder;
}
/**
* @return the pos1
*/
public Location getPos1() {
return pos1;
}
/**
* @param pos1 the pos1 to set
*/
public void setPos1(Location pos1) {
origin = null;
this.pos1 = pos1;
}
/**
* @return the pos2
*/
public Location getPos2() {
return pos2;
}
/**
* @param pos2 the pos2 to set
*/
public void setPos2(Location pos2) {
origin = null;
this.pos2 = pos2;
}
/**
* @return the origin
*/
public Location getOrigin() {
return origin;
}
/**
* @param origin the origin to set
*/
public void setOrigin(Location origin) {
this.origin = origin;
}
/**
* Copy the blocks between pos1 and pos2 to the clipboard
* @param user - user
* @return true if successful, false if pos1 or pos2 are undefined
*/
public boolean copy(User user, boolean copyAir) {
if (pos1 == null || pos2 == null) {
user.sendMessage("commands.admin.schem.need-pos1-pos2");
return false;
}
// World
World world = pos1.getWorld();
// Clear the clipboard
blockConfig = new YamlConfiguration();
int count = 0;
int minX = Math.max(pos1.getBlockX(),pos2.getBlockX());
int maxX = Math.min(pos1.getBlockX(), pos2.getBlockX());
int minY = Math.max(pos1.getBlockY(),pos2.getBlockY());
int maxY = Math.min(pos1.getBlockY(), pos2.getBlockY());
int minZ = Math.max(pos1.getBlockZ(),pos2.getBlockZ());
int maxZ = Math.min(pos1.getBlockZ(), pos2.getBlockZ());
for (int x = Math.min(pos1.getBlockX(), pos2.getBlockX()); x <= Math.max(pos1.getBlockX(),pos2.getBlockX()); x++) {
for (int y = Math.min(pos1.getBlockY(), pos2.getBlockY()); y <= Math.max(pos1.getBlockY(),pos2.getBlockY()); y++) {
for (int z = Math.min(pos1.getBlockZ(), pos2.getBlockZ()); z <= Math.max(pos1.getBlockZ(),pos2.getBlockZ()); z++) {
Block block = world.getBlockAt(x, y, z);
if (copyBlock(block, origin == null ? user.getLocation() : origin, copyAir, world.getLivingEntities().stream()
.filter(Objects::nonNull)
.filter(e -> !(e instanceof Player) && e.getLocation().getBlock().equals(block))
.collect(Collectors.toList()))) {
minX = Math.min(minX, x);
maxX = Math.max(maxX, x);
minY = Math.min(minY, y);
maxY = Math.max(maxY, y);
minZ = Math.min(minZ, z);
maxZ = Math.max(maxZ, z);
count ++;
}
}
}
}
blockConfig.set("size.xsize", maxX - minX + 1);
blockConfig.set("size.ysize", maxY - minY + 1);
blockConfig.set("size.zsize", maxZ - minZ + 1);
user.sendMessage("commands.admin.schem.copied-blocks", TextVariables.NUMBER, String.valueOf(count));
copied = true;
return true;
}
/**
* Pastes the clipboard to island location
* @param world - world in which to paste
* @param island - location to paste
* @param task - task to run after pasting
*/
public void pasteIsland(World world, Island island, Runnable task) {
// Offset due to bedrock
Vector off = new Vector(0,0,0);
if (blockConfig.contains(BEDROCK)) {
String[] offset = blockConfig.getString(BEDROCK).split(",");
off = new Vector(Integer.valueOf(offset[0]), Integer.valueOf(offset[1]), Integer.valueOf(offset[2]));
}
// Calculate location for pasting
Location loc = island.getCenter().toVector().subtract(off).toLocation(world);
// Paste
if (blockConfig.contains(BLOCK)) {
blockConfig.getConfigurationSection(BLOCK).getKeys(false).forEach(b -> pasteBlock(world, island, loc, blockConfig.getConfigurationSection(BLOCK + "." + b)));
} else {
plugin.logError("Clipboard has no block data in it to paste!");
}
// Run follow on task if it exists
if (task != null) {
Bukkit.getScheduler().runTaskLater(plugin, task, 2L);
}
}
/**
* Paste clipboard at this location
* @param location - location
*/
public void pasteClipboard(Location location) {
if (blockConfig.contains(BLOCK)) {
blockConfig.getConfigurationSection(BLOCK).getKeys(false).forEach(b -> pasteBlock(location.getWorld(), null, location, blockConfig.getConfigurationSection(BLOCK + "." + b)));
} else {
plugin.logError("Clipboard has no block data in it to paste!");
}
}
private void writeSign(Island island, Block block, List<String> lines) {
Sign sign = (Sign) block.getState();
org.bukkit.material.Sign s = (org.bukkit.material.Sign) sign.getData();
// Handle spawn sign
if (island != null && !lines.isEmpty() && lines.get(0).equalsIgnoreCase(TextVariables.SPAWN_HERE)) {
block.setType(Material.AIR);
// Orient to face same direction as sign
Location spawnPoint = new Location(block.getWorld(), block.getX() + 0.5D, block.getY(),
block.getZ() + 0.5D, Util.blockFaceToFloat(s.getFacing().getOppositeFace()), 30F);
island.setSpawnPoint(block.getWorld().getEnvironment(), spawnPoint);
return;
}
String name = TextVariables.NAME;
if (island != null) {
name = plugin.getPlayers().getName(island.getOwner());
}
// Sub in player's name
for (int i = 0 ; i < lines.size(); i++) {
sign.setLine(i, lines.get(i).replace(TextVariables.NAME, name));
}
sign.update();
}
private void pasteBlock(World world, Island island, Location location, ConfigurationSection config) {
String[] pos = config.getName().split(",");
int x = location.getBlockX() + Integer.valueOf(pos[0]);
int y = location.getBlockY() + Integer.valueOf(pos[1]);
int z = location.getBlockZ() + Integer.valueOf(pos[2]);
Block block = world.getBlockAt(x, y, z);
String blockData = config.getString("bd");
if (blockData != null) {
if (config.getBoolean(ATTACHED)) {
plugin.getServer().getScheduler().runTask(plugin, () -> setBlock(island, block, config, blockData));
} else {
setBlock(island, block, config, blockData);
}
}
// Entities
if (config.isConfigurationSection(ENTITY)) {
setEntity(island, block.getLocation(), config);
}
}
private void setBlock(Island island, Block block, ConfigurationSection config, String blockData) {
// Set the block data
block.setBlockData(Bukkit.createBlockData(blockData));
// Set the block state for chests, signs and mob spawners
setBlockState(island, block, config);
}
/**
* Sets any entity that is in this location
* @param island - island
* @param location - locaton
* @param config - config section
*/
private void setEntity(Island island, Location location, ConfigurationSection config) {
ConfigurationSection en = config.getConfigurationSection(ENTITY);
en.getKeys(false).forEach(k -> {
ConfigurationSection ent = en.getConfigurationSection(k);
Location center = location.add(new Vector(0.5, 0.0, 0.5));
LivingEntity e = (LivingEntity)location.getWorld().spawnEntity(center, EntityType.valueOf(ent.getString("type", "PIG")));
if (e != null) {
e.setCustomName(ent.getString("name"));
}
if (e instanceof Colorable && ent.contains(COLOR)) {
((Colorable) e).setColor(DyeColor.valueOf(ent.getString(COLOR)));
}
if (e instanceof Tameable) {
((Tameable)e).setTamed(ent.getBoolean("tamed"));
}
if (e instanceof ChestedHorse) {
((ChestedHorse)e).setCarryingChest(ent.getBoolean("chest"));
}
if (e instanceof Ageable) {
if (ent.getBoolean("adult")) {
((Ageable)e).setAdult();
} else {
((Ageable)e).setBaby();
}
}
if (e instanceof AbstractHorse) {
Horse horse = (Horse)e;
horse.setDomestication(ent.getInt("domestication"));
ConfigurationSection inv = ent.getConfigurationSection(INVENTORY);
inv.getKeys(false).forEach(i -> horse.getInventory().setItem(Integer.valueOf(i), (ItemStack)inv.get(i)));
horse.setStyle(Horse.Style.valueOf(ent.getString("style", "NONE")));
}
});
}
/**
* Handles signs, chests and mob spawner blocks
* @param island - island
* @param block - block
* @param config - config
*/
private void setBlockState(Island island, Block block, ConfigurationSection config) {
// Get the block state
BlockState bs = block.getState();
// Signs
if (bs instanceof Sign) {
List<String> lines = config.getStringList("lines");
writeSign(island, block, lines);
}
// Chests, in general
if (bs instanceof InventoryHolder) {
bs.update(true, false);
Inventory ih = ((InventoryHolder)bs).getInventory();
if (config.isConfigurationSection(INVENTORY)) {
ConfigurationSection inv = config.getConfigurationSection(INVENTORY);
inv.getKeys(false).forEach(i -> ih.setItem(Integer.valueOf(i), (ItemStack)inv.get(i)));
}
}
// Mob spawners
if (bs instanceof CreatureSpawner) {
CreatureSpawner spawner = ((CreatureSpawner) bs);
spawner.setSpawnedType(EntityType.valueOf(config.getString("spawnedType", "PIG")));
spawner.setMaxNearbyEntities(config.getInt("maxNearbyEntities", 16));
spawner.setMaxSpawnDelay(config.getInt("maxSpawnDelay", 2*60*20));
spawner.setMinSpawnDelay(config.getInt("minSpawnDelay", 5*20));
spawner.setDelay(config.getInt("delay", -1));
spawner.setRequiredPlayerRange(config.getInt("requiredPlayerRange", 16));
spawner.setSpawnRange(config.getInt("spawnRange", 4));
bs.update(true, false);
}
}
private boolean copyBlock(Block block, Location copyOrigin, boolean copyAir, Collection<LivingEntity> entities) {
if (!copyAir && block.getType().equals(Material.AIR) && entities.isEmpty()) {
return false;
}
// Create position
int x = block.getLocation().getBlockX() - copyOrigin.getBlockX();
int y = block.getLocation().getBlockY() - copyOrigin.getBlockY();
int z = block.getLocation().getBlockZ() - copyOrigin.getBlockZ();
String pos = x + "," + y + "," + z;
// Position defines the section
ConfigurationSection s = blockConfig.createSection(BLOCK + "." + pos);
// Set entities
for (LivingEntity e: entities) {
ConfigurationSection en = s.createSection("entity." + e.getUniqueId());
en.set("type", e.getType().name());
en.set("name", e.getCustomName());
if (e instanceof Colorable) {
Colorable c = (Colorable)e;
en.set(COLOR, c.getColor().name());
}
if (e instanceof Tameable && ((Tameable)e).isTamed()) {
en.set("tamed", true);
}
if (e instanceof ChestedHorse && ((ChestedHorse)e).isCarryingChest()) {
en.set("chest", true);
}
if (e instanceof Ageable) {
en.set("adult", ((Ageable)e).isAdult());
}
if (e instanceof AbstractHorse) {
AbstractHorse horse = (AbstractHorse)e;
en.set("domestication", horse.getDomestication());
for (int index = 0; index < horse.getInventory().getSize(); index++) {
ItemStack i = horse.getInventory().getItem(index);
if (i != null) {
en.set("inventory." + index, i);
}
}
}
if (e instanceof Horse) {
Horse horse = (Horse)e;
en.set("style", horse.getStyle().name());
}
}
// Return if this is just air block
if (!copyAir && block.getType().equals(Material.AIR) && !entities.isEmpty()) {
return true;
}
// Set block data
s.set("bd", block.getBlockData().getAsString());
if (block.getType().equals(Material.BEDROCK)) {
blockConfig.set(BEDROCK, x + "," + y + "," + z);
}
// Block state
BlockState bs = block.getState();
// Chests
if (bs instanceof InventoryHolder) {
InventoryHolder ih = (InventoryHolder)bs;
for (int index = 0; index < ih.getInventory().getSize(); index++) {
ItemStack i = ih.getInventory().getItem(index);
if (i != null) {
s.set("inventory." + index, i);
}
}
}
// Signs
if (bs instanceof Sign) {
Sign sign = (Sign)bs;
s.set("lines", Arrays.asList(sign.getLines()));
}
if (bs instanceof CreatureSpawner) {
CreatureSpawner spawner = (CreatureSpawner)bs;
s.set("spawnedType",spawner.getSpawnedType().name());
s.set("delay", spawner.getDelay());
s.set("maxNearbyEntities", spawner.getMaxNearbyEntities());
s.set("maxSpawnDelay", spawner.getMaxSpawnDelay());
s.set("minSpawnDelay", spawner.getMinSpawnDelay());
s.set("requiredPlayerRange", spawner.getRequiredPlayerRange());
s.set("spawnRange", spawner.getSpawnRange());
}
return true;
}
/**
* @return the blockConfig
*/
private YamlConfiguration getBlockConfig() {
return blockConfig;
}
private void unzip(final String zipFilePath) throws IOException {
Path path = Paths.get(zipFilePath);
if (!(path.toFile().exists())) {
throw new IOException("No file exists!");
}
try (ZipInputStream zipInputStream = new ZipInputStream(new FileInputStream(zipFilePath))) {
ZipEntry entry = zipInputStream.getNextEntry();
while (entry != null) {
Path filePath = Paths.get(path.getParent().toString(), entry.getName());
if (!entry.isDirectory()) {
unzipFiles(zipInputStream, filePath);
} else {
Files.createDirectories(filePath);
}
zipInputStream.closeEntry();
entry = zipInputStream.getNextEntry();
}
}
}
private static void unzipFiles(final ZipInputStream zipInputStream, final Path unzipFilePath) throws IOException {
try (BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(unzipFilePath.toAbsolutePath().toString()))) {
byte[] bytesIn = new byte[1024];
int read;
while ((read = zipInputStream.read(bytesIn)) != -1) {
bos.write(bytesIn, 0, read);
}
}
}
private void zip(File targetFile) throws IOException {
try (ZipOutputStream zipOutputStream = new ZipOutputStream(new FileOutputStream(targetFile.getAbsolutePath() + ".schem"))) {
zipOutputStream.putNextEntry(new ZipEntry(targetFile.getName()));
try (FileInputStream inputStream = new FileInputStream(targetFile)) {
final byte[] buffer = new byte[1024];
int length;
while((length = inputStream.read(buffer)) >= 0) {
zipOutputStream.write(buffer, 0, length);
}
try {
Files.delete(targetFile.toPath());
} catch (Exception e) {
plugin.logError(e.getMessage());
}
}
}
}
public boolean isFull() {
return copied;
}
/**
* Load a file to clipboard
* @param fileName - filename in schems folder
* @throws IOException - if there's a load error with unziping or name
* @throws InvalidConfigurationException - the YAML of the schem is at fault
*/
public void load(String fileName) throws IOException, InvalidConfigurationException {
File zipFile = new File(schemFolder, fileName + ".schem");
if (!zipFile.exists()) {
plugin.logError(LOAD_ERROR + zipFile.getName());
throw new IOException(LOAD_ERROR + zipFile.getName());
}
unzip(zipFile.getAbsolutePath());
File file = new File(schemFolder, fileName);
if (!file.exists()) {
plugin.logError(LOAD_ERROR + file.getName());
throw new IOException(LOAD_ERROR + file.getName());
}
blockConfig = new YamlConfiguration();
blockConfig.load(file);
copied = true;
Files.delete(file.toPath());
}
/*
Load a file to clipboard
*/
/**
* @param user - use trying to load
* @param fileName - filename
* @return - ture if load is successful, false if not
*/
public boolean load(User user, String fileName) {
try {
load(fileName);
} catch (IOException e1) {
user.sendMessage("commands.admin.schem.could-not-load");
plugin.logError("Could not load schems file: " + fileName + " " + e1.getMessage());
return false;
} catch (InvalidConfigurationException e1) {
user.sendMessage("commands.admin.schem.could-not-load");
plugin.logError("Could not load schems file - YAML error : " + fileName + " " + e1.getMessage());
return false;
}
user.sendMessage("general.success");
return true;
}
/**
* Save the clipboard to a file
* @param user - user who is copying
* @param newFile - filename
* @return - true if successful, false if error
*/
public boolean save(User user, String newFile) {
File file = new File(schemFolder, newFile);
try {
getBlockConfig().save(file);
} catch (IOException e) {
user.sendMessage("commands.admin.schem.could-not-save", "[message]", "Could not save temp schems file.");
plugin.logError("Could not save temporary schems file: " + file.getName());
return false;
}
try {
zip(file);
} catch (IOException e) {
user.sendMessage("commands.admin.schem.could-not-save", "[message]", "Could not zip temp schems file.");
plugin.logError("Could not zip temporary schems file: " + file.getName());
return false;
}
user.sendMessage("general.success");
return true;
}
}