Skip to content

Instantly share code, notes, and snippets.

@Kage0x3B
Created September 24, 2017 16:37
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save Kage0x3B/b7a0880652dba8695928ff9220d6e87c to your computer and use it in GitHub Desktop.
Save Kage0x3B/b7a0880652dba8695928ff9220d6e87c to your computer and use it in GitHub Desktop.
package de.syscy.kagecore.util;
import java.util.HashMap;
import java.util.Map;
import org.bukkit.Location;
import org.bukkit.configuration.serialization.ConfigurationSerializable;
import com.sk89q.worldedit.bukkit.selections.CuboidSelection;
import lombok.Getter;
import lombok.ToString;
@ToString
public class BoundingBox implements ConfigurationSerializable {
private @Getter Location min;
private @Getter Location max;
public BoundingBox(Map<String, Object> map) {
this((Location) map.get("min"), (Location) map.get("max"));
}
public BoundingBox(CuboidSelection selection) {
this(selection.getMinimumPoint(), selection.getMaximumPoint());
}
public BoundingBox(Location min, Location max) {
if(min == null || max == null) {
throw new IllegalArgumentException("The min/max locations can't be null");
}
if(min.getWorld() != max.getWorld()) {
throw new IllegalArgumentException("Both provided locations need to be in the same world");
}
this.min = new Location(min.getWorld(), Math.min(min.getX(), max.getX()), Math.min(min.getY(), max.getY()), Math.min(min.getZ(), max.getZ()));
this.max = new Location(min.getWorld(), Math.max(min.getX(), max.getX()), Math.max(min.getY(), max.getY()), Math.max(min.getZ(), max.getZ()));
}
public boolean contains(Location loc) {
return (min.getWorld() == null || loc.getWorld() == min.getWorld()) && min.getX() <= loc.getX() && max.getX() >= loc.getX() - 1 && min.getY() <= loc.getY() && max.getY() >= loc.getY() - 1 && min.getZ() <= loc.getZ() && max.getZ() >= loc.getZ() - 1;
}
@Override
public Map<String, Object> serialize() {
Map<String, Object> map = new HashMap<>();
map.put("min", min);
map.put("max", max);
return map;
}
}
/*
* WorldEdit, a Minecraft world manipulation toolkit
* Copyright (C) sk89q <http://www.sk89q.com>
* Copyright (C) WorldEdit team and contributors
*
* This program is free software: you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as published by the
* Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
* for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package de.syscy.kagecore.worldedit;
import static com.google.common.base.Preconditions.checkNotNull;
import com.sk89q.worldedit.Vector;
import com.sk89q.worldedit.extent.Extent;
import com.sk89q.worldedit.extent.clipboard.Clipboard;
import com.sk89q.worldedit.extent.transform.BlockTransformExtent;
import com.sk89q.worldedit.function.operation.ForwardExtentCopy;
import com.sk89q.worldedit.function.operation.Operation;
import com.sk89q.worldedit.math.transform.AffineTransform;
import com.sk89q.worldedit.math.transform.CombinedTransform;
import com.sk89q.worldedit.math.transform.Transform;
import com.sk89q.worldedit.regions.CuboidRegion;
import com.sk89q.worldedit.regions.Region;
import com.sk89q.worldedit.world.registry.WorldData;
/**
* Helper class to 'bake' a transform into a clipboard.
*
* <p>This class needs a better name and may need to be made more generic.</p>
*
* @see Clipboard
* @see Transform
*/
public class FlattenedClipboardTransform {
private final Clipboard original;
private final Transform transform;
private final WorldData worldData;
/**
* Create a new instance.
*
* @param original the original clipboard
* @param transform the transform
* @param worldData the world data instance
*/
private FlattenedClipboardTransform(Clipboard original, Transform transform, WorldData worldData) {
checkNotNull(original);
checkNotNull(transform);
checkNotNull(worldData);
this.original = original;
this.transform = transform;
this.worldData = worldData;
}
/**
* Get the transformed region.
*
* @return the transformed region
*/
public Region getTransformedRegion() {
Region region = original.getRegion();
Vector minimum = region.getMinimumPoint();
Vector maximum = region.getMaximumPoint();
Transform transformAround = new CombinedTransform(new AffineTransform().translate(original.getOrigin().multiply(-1)), transform, new AffineTransform().translate(original.getOrigin()));
Vector[] corners = new Vector[] { minimum, maximum, minimum.setX(maximum.getX()), minimum.setY(maximum.getY()), minimum.setZ(maximum.getZ()), maximum.setX(minimum.getX()), maximum.setY(minimum.getY()), maximum.setZ(minimum.getZ()) };
for(int i = 0; i < corners.length; i++) {
corners[i] = transformAround.apply(corners[i]);
}
Vector newMinimum = corners[0];
Vector newMaximum = corners[0];
for(int i = 1; i < corners.length; i++) {
newMinimum = Vector.getMinimum(newMinimum, corners[i]);
newMaximum = Vector.getMaximum(newMaximum, corners[i]);
}
// After transformation, the points may not really sit on a block,
// so we should expand the region for edge cases
newMinimum = newMinimum.setX(Math.floor(newMinimum.getX()));
newMinimum = newMinimum.setY(Math.floor(newMinimum.getY()));
newMinimum = newMinimum.setZ(Math.floor(newMinimum.getZ()));
newMaximum = newMaximum.setX(Math.ceil(newMaximum.getX()));
newMaximum = newMaximum.setY(Math.ceil(newMaximum.getY()));
newMaximum = newMaximum.setZ(Math.ceil(newMaximum.getZ()));
return new CuboidRegion(newMinimum, newMaximum);
}
/**
* Create an operation to copy from the original clipboard to the given extent.
*
* @param target the target
* @return the operation
*/
public Operation copyTo(Extent target) {
BlockTransformExtent extent = new BlockTransformExtent(original, transform, worldData.getBlockRegistry());
ForwardExtentCopy copy = new ForwardExtentCopy(extent, original.getRegion(), original.getOrigin(), target, original.getOrigin());
copy.setTransform(transform);
return copy;
}
/**
* Create a new instance to bake the transform with.
*
* @param original the original clipboard
* @param transform the transform
* @param worldData the world data instance
* @return a builder
*/
public static FlattenedClipboardTransform transform(Clipboard original, Transform transform, WorldData worldData) {
return new FlattenedClipboardTransform(original, transform, worldData);
}
}
package de.syscy.kagecore.worldedit;
import org.bukkit.Location;
import com.sk89q.worldedit.EditSession;
import com.sk89q.worldedit.MaxChangedBlocksException;
import com.sk89q.worldedit.Vector;
import com.sk89q.worldedit.WorldEditException;
import com.sk89q.worldedit.extent.clipboard.BlockArrayClipboard;
import com.sk89q.worldedit.extent.clipboard.Clipboard;
import com.sk89q.worldedit.function.operation.Operation;
import com.sk89q.worldedit.function.operation.Operations;
import com.sk89q.worldedit.math.transform.AffineTransform;
import com.sk89q.worldedit.session.ClipboardHolder;
import com.sk89q.worldedit.world.registry.WorldData;
import lombok.AccessLevel;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.Setter;
@AllArgsConstructor(access = AccessLevel.PROTECTED)
public class Schematic {
private @Getter @Setter EditSession editSession;
private @Getter @Setter ClipboardHolder clipboardHolder;
public void rotateX(double theta) {
rotate(theta, 0, 0);
}
public void rotateY(double theta) {
rotate(0, theta, 0);
}
public void rotateZ(double theta) {
rotate(0, 0, theta);
}
public void rotate(double x, double y, double z) {
AffineTransform transform = new AffineTransform();
if(x != 0) {
transform = transform.rotateX(x);
}
if(y != 0) {
transform = transform.rotateY(y);
}
if(z != 0) {
transform = transform.rotateZ(z);
}
clipboardHolder.setTransform(clipboardHolder.getTransform().combine(transform));
}
/**
* Applies the current transform to the clipboard.
* Usually this only happens immediately before the schematic is pasted.
*/
public void bakeTransform() throws WorldEditException, MaxChangedBlocksException {
WorldData worldData = editSession.getWorld().getWorldData();
FlattenedClipboardTransform result = FlattenedClipboardTransform.transform(clipboardHolder.getClipboard(), clipboardHolder.getTransform(), worldData);
Clipboard newClipboard = new BlockArrayClipboard(result.getTransformedRegion());
newClipboard.setOrigin(clipboardHolder.getClipboard().getOrigin());
Operations.complete(result.copyTo(newClipboard));
clipboardHolder = new ClipboardHolder(newClipboard, worldData);
}
public void paste(Location location) throws WorldEditException {
paste(location, true);
}
public void paste(Location location, boolean ignoreAirBlocks) throws WorldEditException {
editSession.setBlockChangeLimit(Integer.MAX_VALUE);
Vector position = new Vector(location.getBlockX(), location.getBlockY(), location.getBlockZ());
Operation pasteOperation = clipboardHolder.createPaste(editSession, clipboardHolder.getWorldData()).to(position).ignoreAirBlocks(ignoreAirBlocks).build();
Operations.complete(pasteOperation);
}
}
package de.syscy.kagecore.worldedit;
import java.io.BufferedInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import de.syscy.kagecore.util.BoundingBox;
import org.bukkit.Location;
import org.bukkit.World;
import com.sk89q.worldedit.EditSession;
import com.sk89q.worldedit.MaxChangedBlocksException;
import com.sk89q.worldedit.Vector;
import com.sk89q.worldedit.extent.clipboard.BlockArrayClipboard;
import com.sk89q.worldedit.extent.clipboard.Clipboard;
import com.sk89q.worldedit.extent.clipboard.io.ClipboardFormat;
import com.sk89q.worldedit.extent.clipboard.io.ClipboardReader;
import com.sk89q.worldedit.function.operation.ForwardExtentCopy;
import com.sk89q.worldedit.function.operation.Operations;
import com.sk89q.worldedit.regions.CuboidRegion;
import com.sk89q.worldedit.session.ClipboardHolder;
import com.sk89q.worldedit.util.io.Closer;
import com.sk89q.worldedit.world.registry.WorldData;
import lombok.experimental.UtilityClass;
@UtilityClass
public class SchematicLoader {
/**
* Loads a schematic from the plugins/WorldEdit/schematics folder
* @param world The world in which the schematic will be used
* @param schematicName The file name in the schematics folder
*/
public static Schematic load(World world, String schematicName) throws IOException {
if(!WorldEditUtil.initWorldEdit()) {
return null;
}
return load(world, WorldEditUtil.getWorldEdit().getWorkingDirectoryFile("schematics/" + schematicName));
}
/**
* Loads a schematic from a file
* @param world The world in which the schematic will be used
* @param file
*/
public static Schematic load(World world, File schematicFile) throws IOException {
if(!WorldEditUtil.initWorldEdit()) {
return null;
}
com.sk89q.worldedit.world.World weWorld = WorldEditUtil.getWEWorld(world);
WorldData worldData = weWorld.getWorldData();
ClipboardFormat format = ClipboardFormat.findByFile(schematicFile);
Closer closer = Closer.create();
BufferedInputStream inputStream = closer.register(new BufferedInputStream(closer.register(new FileInputStream(schematicFile))));
ClipboardReader clipboardReader = format.getReader(inputStream);
Clipboard clipboard = clipboardReader.read(worldData);
ClipboardHolder clipboardHolder = new ClipboardHolder(clipboard, worldData);
closer.close();
return new Schematic(WorldEditUtil.createSession(weWorld), new ClipboardHolder(clipboardHolder.getClipboard(), clipboardHolder.getWorldData()));
}
/**
* Loads an area in the world into a schematic (for saving it)
* @param world The world in which the schematic will be used
* @param boundingBox The bounding box of the area
* @param origin An origin, used for rotating and when pasting the schematic again. Usually set to (0,0) or the (bottom-)center of the area
* @return a {@link Schematic}
*/
public static Schematic loadArea(World world, BoundingBox boundingBox, org.bukkit.util.Vector origin) {
if(!WorldEditUtil.initWorldEdit()) {
return null;
}
com.sk89q.worldedit.world.World weWorld = WorldEditUtil.getWEWorld(world);
CuboidRegion region = new CuboidRegion(toVector(boundingBox.getMin()), toVector(boundingBox.getMax()));
EditSession editSession = WorldEditUtil.createSession(weWorld);
BlockArrayClipboard clipboard = new BlockArrayClipboard(region);
clipboard.setOrigin(new Vector(origin.getBlockX(), origin.getBlockY(), origin.getBlockZ()));
ForwardExtentCopy copy = new ForwardExtentCopy(editSession, region, clipboard, region.getMinimumPoint());
try {
Operations.completeLegacy(copy);
} catch(MaxChangedBlocksException ex) {
return null;
}
return new Schematic(editSession, new ClipboardHolder(clipboard, editSession.getWorld().getWorldData()));
}
private static Vector toVector(Location location) {
return new Vector(location.getBlockX(), location.getBlockY(), location.getBlockZ());
}
}
package de.syscy.kagecore.worldedit;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import com.sk89q.worldedit.extent.clipboard.Clipboard;
import com.sk89q.worldedit.extent.clipboard.io.ClipboardFormat;
import com.sk89q.worldedit.extent.clipboard.io.ClipboardWriter;
import com.sk89q.worldedit.session.ClipboardHolder;
import com.sk89q.worldedit.util.io.Closer;
import lombok.experimental.UtilityClass;
@UtilityClass
public class SchematicSaver {
/**
* Saves a schematic to a file (Ignores any transforms right now)
* @param schematic The schematic
* @param schematicFile The file to save the schematic to
*/
public static boolean save(Schematic schematic, File schematicFile) throws IOException {
if(!WorldEditUtil.initWorldEdit()) {
return false;
}
ClipboardHolder clipboardHolder = schematic.getClipboardHolder();
Clipboard clipboard = clipboardHolder.getClipboard();
Closer closer = Closer.create();
File parent = schematicFile.getParentFile();
if(parent != null && !parent.exists()) {
if(!parent.mkdirs()) {
throw new IOException("Could not create folder for the schematic!");
}
}
FileOutputStream fileOutputStream = closer.register(new FileOutputStream(schematicFile));
BufferedOutputStream bufferedOutputStream = closer.register(new BufferedOutputStream(fileOutputStream));
ClipboardWriter clipboardWriter = closer.register(ClipboardFormat.SCHEMATIC.getWriter(bufferedOutputStream));
clipboardWriter.write(clipboard, clipboardHolder.getWorldData());
try {
closer.close();
} catch(IOException ignored) {
}
return true;
}
}
package de.syscy.kagecore.worldedit;
import java.util.HashMap;
import java.util.Map;
import java.util.logging.Logger;
import org.bukkit.Bukkit;
import org.bukkit.plugin.Plugin;
import com.sk89q.worldedit.EditSession;
import com.sk89q.worldedit.WorldEdit;
import com.sk89q.worldedit.bukkit.BukkitWorld;
import com.sk89q.worldedit.bukkit.WorldEditPlugin;
import com.sk89q.worldedit.world.World;
import lombok.AccessLevel;
import lombok.Getter;
import lombok.experimental.UtilityClass;
@UtilityClass
public class WorldEditUtil {
private static @Getter WorldEditPlugin worldEditPlugin;
private static @Getter WorldEdit worldEdit;
private static @Getter(value = AccessLevel.PROTECTED) Map<org.bukkit.World, World> worldCache = new HashMap<>();
protected static boolean initWorldEdit() {
if(worldEditPlugin == null) {
Plugin plugin = Bukkit.getPluginManager().getPlugin("WorldEdit");
if(plugin != null) {
worldEditPlugin = (WorldEditPlugin) plugin;
worldEdit = worldEditPlugin.getWorldEdit();
} else {
Logger.getLogger("KageCore").info("WorldEdit is not installed!");
return false;
}
}
return true;
}
public static World getWEWorld(org.bukkit.World world) {
if(!initWorldEdit()) {
return null;
}
if(worldCache.containsKey(world)) {
return worldCache.get(world);
}
World weWorld = new BukkitWorld(world);
worldCache.put(world, weWorld);
return weWorld;
}
public static EditSession createSession(World world) {
return worldEdit.getEditSessionFactory().getEditSession(world, Integer.MAX_VALUE);
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment