Created
February 13, 2016 20:30
-
-
Save hugo4715/70fece705062940dbf1c to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package fr.hugo4715.paytoprotect.utils; | |
import java.util.ArrayList; | |
import java.util.HashMap; | |
import java.util.Iterator; | |
import java.util.List; | |
import java.util.Map; | |
import org.bukkit.Bukkit; | |
import org.bukkit.Chunk; | |
import org.bukkit.Location; | |
import org.bukkit.World; | |
import org.bukkit.block.Block; | |
import org.bukkit.configuration.serialization.ConfigurationSerializable; | |
/** | |
* This class is a region/cuboid from one location to another. It can be used for blocks protection and things like WorldEdit. | |
* From: https://bukkit.org/threads/protection-region-cuboid-creation.164161/ | |
* @author desht (Original code), KingFaris10 (Editor of code) | |
*/ | |
public class Cuboid implements Iterable<Block>, Cloneable, ConfigurationSerializable { | |
protected final String worldName; | |
protected final int x1, y1, z1; | |
protected final int x2, y2, z2; | |
/** | |
* Construct a Cuboid given two Location objects which represent any two corners of the Cuboid. | |
* Note: The 2 locations must be on the same world. | |
* | |
* @param l1 - One of the corners | |
* @param l2 - The other corner | |
*/ | |
public Cuboid(Location l1, Location l2) { | |
if (!l1.getWorld().equals(l2.getWorld())) throw new IllegalArgumentException("Locations must be on the same world"); | |
this.worldName = l1.getWorld().getName(); | |
this.x1 = Math.min(l1.getBlockX(), l2.getBlockX()); | |
this.y1 = Math.min(l1.getBlockY(), l2.getBlockY()); | |
this.z1 = Math.min(l1.getBlockZ(), l2.getBlockZ()); | |
this.x2 = Math.max(l1.getBlockX(), l2.getBlockX()); | |
this.y2 = Math.max(l1.getBlockY(), l2.getBlockY()); | |
this.z2 = Math.max(l1.getBlockZ(), l2.getBlockZ()); | |
} | |
/** | |
* Construct a one-block Cuboid at the given Location of the Cuboid. | |
* | |
* @param l1 location of the Cuboid | |
*/ | |
public Cuboid(Location l1) { | |
this(l1, l1); | |
} | |
/** | |
* Copy constructor. | |
* | |
* @param other - The Cuboid to copy | |
*/ | |
public Cuboid(Cuboid other) { | |
this(other.getWorld().getName(), other.x1, other.y1, other.z1, other.x2, other.y2, other.z2); | |
} | |
/** | |
* Construct a Cuboid in the given World and xyz co-ordinates | |
* | |
* @param world - The Cuboid's world | |
* @param x1 - X co-ordinate of corner 1 | |
* @param y1 - Y co-ordinate of corner 1 | |
* @param z1 - Z co-ordinate of corner 1 | |
* @param x2 - X co-ordinate of corner 2 | |
* @param y2 - Y co-ordinate of corner 2 | |
* @param z2 - Z co-ordinate of corner 2 | |
*/ | |
public Cuboid(World world, int x1, int y1, int z1, int x2, int y2, int z2) { | |
this.worldName = world.getName(); | |
this.x1 = Math.min(x1, x2); | |
this.x2 = Math.max(x1, x2); | |
this.y1 = Math.min(y1, y2); | |
this.y2 = Math.max(y1, y2); | |
this.z1 = Math.min(z1, z2); | |
this.z2 = Math.max(z1, z2); | |
} | |
/** | |
* Construct a Cuboid in the given world name and xyz co-ordinates. | |
* | |
* @param worldName - The Cuboid's world name | |
* @param x1 - X co-ordinate of corner 1 | |
* @param y1 - Y co-ordinate of corner 1 | |
* @param z1 - Z co-ordinate of corner 1 | |
* @param x2 - X co-ordinate of corner 2 | |
* @param y2 - Y co-ordinate of corner 2 | |
* @param z2 - Z co-ordinate of corner 2 | |
*/ | |
private Cuboid(String worldName, int x1, int y1, int z1, int x2, int y2, int z2) { | |
this.worldName = worldName; | |
this.x1 = Math.min(x1, x2); | |
this.x2 = Math.max(x1, x2); | |
this.y1 = Math.min(y1, y2); | |
this.y2 = Math.max(y1, y2); | |
this.z1 = Math.min(z1, z2); | |
this.z2 = Math.max(z1, z2); | |
} | |
/** | |
* Construct a Cuboid using a map with the following keys: worldName, x1, x2, y1, y2, z1, z2 | |
* @param map - The map of keys. | |
*/ | |
public Cuboid(Map<String, Object> map) { | |
this.worldName = (String) map.get("worldName"); | |
this.x1 = (Integer) map.get("x1"); | |
this.x2 = (Integer) map.get("x2"); | |
this.y1 = (Integer) map.get("y1"); | |
this.y2 = (Integer) map.get("y2"); | |
this.z1 = (Integer) map.get("z1"); | |
this.z2 = (Integer) map.get("z2"); | |
} | |
@Override | |
public Map<String, Object> serialize() { | |
Map<String, Object> map = new HashMap<String, Object>(); | |
map.put("worldName", this.worldName); | |
map.put("x1", this.x1); | |
map.put("y1", this.y1); | |
map.put("z1", this.z1); | |
map.put("x2", this.x2); | |
map.put("y2", this.y2); | |
map.put("z2", this.z2); | |
return map; | |
} | |
/** | |
* Get the Location of the lower northeast corner of the Cuboid (minimum XYZ co-ordinates). | |
* | |
* @return Location of the lower northeast corner | |
*/ | |
public Location getLowerNE() { | |
return new Location(this.getWorld(), this.x1, this.y1, this.z1); | |
} | |
/** | |
* Get the Location of the upper southwest corner of the Cuboid (maximum XYZ co-ordinates). | |
* | |
* @return Location of the upper southwest corner | |
*/ | |
public Location getUpperSW() { | |
return new Location(this.getWorld(), this.x2, this.y2, this.z2); | |
} | |
/** | |
* Get the blocks in the Cuboid. | |
* | |
* @return The blocks in the Cuboid | |
*/ | |
public List<Block> getBlocks() { | |
Iterator<Block> blockI = this.iterator(); | |
List<Block> copy = new ArrayList<Block>(); | |
while (blockI.hasNext()) | |
copy.add(blockI.next()); | |
return copy; | |
} | |
/** | |
* Get the the centre of the Cuboid. | |
* | |
* @return Location at the centre of the Cuboid | |
*/ | |
public Location getCenter() { | |
int x1 = this.getUpperX() + 1; | |
int y1 = this.getUpperY() + 1; | |
int z1 = this.getUpperZ() + 1; | |
return new Location(this.getWorld(), this.getLowerX() + (x1 - this.getLowerX()) / 2.0, this.getLowerY() + (y1 - this.getLowerY()) / 2.0, this.getLowerZ() + (z1 - this.getLowerZ()) / 2.0); | |
} | |
/** | |
* Get the Cuboid's world. | |
* | |
* @return The World object representing this Cuboid's world | |
* @throws IllegalStateException if the world is not loaded | |
*/ | |
public World getWorld() { | |
World world = Bukkit.getWorld(this.worldName); | |
if (world == null) throw new IllegalStateException("World '" + this.worldName + "' is not loaded"); | |
return world; | |
} | |
/** | |
* Get the size of this Cuboid along the X axis | |
* | |
* @return Size of Cuboid along the X axis | |
*/ | |
public int getSizeX() { | |
return (this.x2 - this.x1) + 1; | |
} | |
/** | |
* Get the size of this Cuboid along the Y axis | |
* | |
* @return Size of Cuboid along the Y axis | |
*/ | |
public int getSizeY() { | |
return (this.y2 - this.y1) + 1; | |
} | |
/** | |
* Get the size of this Cuboid along the Z axis | |
* | |
* @return Size of Cuboid along the Z axis | |
*/ | |
public int getSizeZ() { | |
return (this.z2 - this.z1) + 1; | |
} | |
/** | |
* Get the minimum X co-ordinate of this Cuboid | |
* | |
* @return the minimum X co-ordinate | |
*/ | |
public int getLowerX() { | |
return this.x1; | |
} | |
/** | |
* Get the minimum Y co-ordinate of this Cuboid | |
* | |
* @return the minimum Y co-ordinate | |
*/ | |
public int getLowerY() { | |
return this.y1; | |
} | |
/** | |
* Get the minimum Z co-ordinate of this Cuboid | |
* | |
* @return the minimum Z co-ordinate | |
*/ | |
public int getLowerZ() { | |
return this.z1; | |
} | |
/** | |
* Get the maximum X co-ordinate of this Cuboid | |
* | |
* @return the maximum X co-ordinate | |
*/ | |
public int getUpperX() { | |
return this.x2; | |
} | |
/** | |
* Get the maximum Y co-ordinate of this Cuboid | |
* | |
* @return the maximum Y co-ordinate | |
*/ | |
public int getUpperY() { | |
return this.y2; | |
} | |
/** | |
* Get the maximum Z co-ordinate of this Cuboid | |
* | |
* @return the maximum Z co-ordinate | |
*/ | |
public int getUpperZ() { | |
return this.z2; | |
} | |
/** | |
* Get the Blocks at the eight corners of the Cuboid. | |
* | |
* @return array of Block objects representing the Cuboid corners | |
*/ | |
public Block[] corners() { | |
Block[] res = new Block[8]; | |
World w = this.getWorld(); | |
res[0] = w.getBlockAt(this.x1, this.y1, this.z1); | |
res[1] = w.getBlockAt(this.x1, this.y1, this.z2); | |
res[2] = w.getBlockAt(this.x1, this.y2, this.z1); | |
res[3] = w.getBlockAt(this.x1, this.y2, this.z2); | |
res[4] = w.getBlockAt(this.x2, this.y1, this.z1); | |
res[5] = w.getBlockAt(this.x2, this.y1, this.z2); | |
res[6] = w.getBlockAt(this.x2, this.y2, this.z1); | |
res[7] = w.getBlockAt(this.x2, this.y2, this.z2); | |
return res; | |
} | |
/** | |
* Expand the Cuboid in the given direction by the given amount. Negative amounts will shrink the Cuboid in the given direction. Shrinking a cuboid's face past the opposite face is not an error and will return a valid Cuboid. | |
* | |
* @param dir - The direction in which to expand | |
* @param amount - The number of blocks by which to expand | |
* @return A new Cuboid expanded by the given direction and amount | |
*/ | |
public Cuboid expand(CuboidDirection dir, int amount) { | |
switch (dir) { | |
case North: | |
return new Cuboid(this.worldName, this.x1 - amount, this.y1, this.z1, this.x2, this.y2, this.z2); | |
case South: | |
return new Cuboid(this.worldName, this.x1, this.y1, this.z1, this.x2 + amount, this.y2, this.z2); | |
case East: | |
return new Cuboid(this.worldName, this.x1, this.y1, this.z1 - amount, this.x2, this.y2, this.z2); | |
case West: | |
return new Cuboid(this.worldName, this.x1, this.y1, this.z1, this.x2, this.y2, this.z2 + amount); | |
case Down: | |
return new Cuboid(this.worldName, this.x1, this.y1 - amount, this.z1, this.x2, this.y2, this.z2); | |
case Up: | |
return new Cuboid(this.worldName, this.x1, this.y1, this.z1, this.x2, this.y2 + amount, this.z2); | |
default: | |
throw new IllegalArgumentException("Invalid direction " + dir); | |
} | |
} | |
/** | |
* Shift the Cuboid in the given direction by the given amount. | |
* | |
* @param dir - The direction in which to shift | |
* @param amount - The number of blocks by which to shift | |
* @return A new Cuboid shifted by the given direction and amount | |
*/ | |
public Cuboid shift(CuboidDirection dir, int amount) { | |
return expand(dir, amount).expand(dir.opposite(), -amount); | |
} | |
/** | |
* Outset (grow) the Cuboid in the given direction by the given amount. | |
* | |
* @param dir - The direction in which to outset (must be Horizontal, Vertical, or Both) | |
* @param amount - The number of blocks by which to outset | |
* @return A new Cuboid outset by the given direction and amount | |
*/ | |
public Cuboid outset(CuboidDirection dir, int amount) { | |
Cuboid c; | |
switch (dir) { | |
case Horizontal: | |
c = expand(CuboidDirection.North, amount).expand(CuboidDirection.South, amount).expand(CuboidDirection.East, amount).expand(CuboidDirection.West, amount); | |
break; | |
case Vertical: | |
c = expand(CuboidDirection.Down, amount).expand(CuboidDirection.Up, amount); | |
break; | |
case Both: | |
c = outset(CuboidDirection.Horizontal, amount).outset(CuboidDirection.Vertical, amount); | |
break; | |
default: | |
throw new IllegalArgumentException("Invalid direction " + dir); | |
} | |
return c; | |
} | |
/** | |
* Inset (shrink) the Cuboid in the given direction by the given amount. Equivalent | |
* to calling outset() with a negative amount. | |
* | |
* @param dir - The direction in which to inset (must be Horizontal, Vertical, or Both) | |
* @param amount - The number of blocks by which to inset | |
* @return A new Cuboid inset by the given direction and amount | |
*/ | |
public Cuboid inset(CuboidDirection dir, int amount) { | |
return this.outset(dir, -amount); | |
} | |
/** | |
* Return true if the point at (x,y,z) is contained within this Cuboid. | |
* | |
* @param x - The X co-ordinate | |
* @param y - The Y co-ordinate | |
* @param z - The Z co-ordinate | |
* @return true if the given point is within this Cuboid, false otherwise | |
*/ | |
public boolean contains(int x, int y, int z) { | |
return x >= this.x1 && x <= this.x2 && y >= this.y1 && y <= this.y2 && z >= this.z1 && z <= this.z2; | |
} | |
/** | |
* Check if the given Block is contained within this Cuboid. | |
* | |
* @param b - The Block to check for | |
* @return true if the Block is within this Cuboid, false otherwise | |
*/ | |
public boolean contains(Block b) { | |
return this.contains(b.getLocation()); | |
} | |
/** | |
* Check if the given Location is contained within this Cuboid. | |
* | |
* @param l - The Location to check for | |
* @return true if the Location is within this Cuboid, false otherwise | |
*/ | |
public boolean contains(Location l) { | |
if (!this.worldName.equals(l.getWorld().getName())) return false; | |
return this.contains(l.getBlockX(), l.getBlockY(), l.getBlockZ()); | |
} | |
/** | |
* Get the volume of this Cuboid. | |
* | |
* @return The Cuboid volume, in blocks | |
*/ | |
public int getVolume() { | |
return this.getSizeX() * this.getSizeY() * this.getSizeZ(); | |
} | |
/** | |
* Get the average light level of all empty (air) blocks in the Cuboid. Returns 0 if there are no empty blocks. | |
* | |
* @return The average light level of this Cuboid | |
*/ | |
public byte getAverageLightLevel() { | |
long total = 0; | |
int n = 0; | |
for (Block b : this) { | |
if (b.isEmpty()) { | |
total += b.getLightLevel(); | |
++n; | |
} | |
} | |
return n > 0 ? (byte) (total / n) : 0; | |
} | |
/** | |
* Contract the Cuboid, returning a Cuboid with any air around the edges removed, just large enough to include all non-air blocks. | |
* | |
* @return A new Cuboid with no external air blocks | |
*/ | |
public Cuboid contract() { | |
return this.contract(CuboidDirection.Down).contract(CuboidDirection.South).contract(CuboidDirection.East).contract(CuboidDirection.Up).contract(CuboidDirection.North).contract(CuboidDirection.West); | |
} | |
/** | |
* Contract the Cuboid in the given direction, returning a new Cuboid which has no exterior empty space. | |
* E.g. A direction of Down will push the top face downwards as much as possible. | |
* | |
* @param dir - The direction in which to contract | |
* @return A new Cuboid contracted in the given direction | |
*/ | |
public Cuboid contract(CuboidDirection dir) { | |
Cuboid face = getFace(dir.opposite()); | |
switch (dir) { | |
case Down: | |
while (face.containsOnly(0) && face.getLowerY() > this.getLowerY()) { | |
face = face.shift(CuboidDirection.Down, 1); | |
} | |
return new Cuboid(this.worldName, this.x1, this.y1, this.z1, this.x2, face.getUpperY(), this.z2); | |
case Up: | |
while (face.containsOnly(0) && face.getUpperY() < this.getUpperY()) { | |
face = face.shift(CuboidDirection.Up, 1); | |
} | |
return new Cuboid(this.worldName, this.x1, face.getLowerY(), this.z1, this.x2, this.y2, this.z2); | |
case North: | |
while (face.containsOnly(0) && face.getLowerX() > this.getLowerX()) { | |
face = face.shift(CuboidDirection.North, 1); | |
} | |
return new Cuboid(this.worldName, this.x1, this.y1, this.z1, face.getUpperX(), this.y2, this.z2); | |
case South: | |
while (face.containsOnly(0) && face.getUpperX() < this.getUpperX()) { | |
face = face.shift(CuboidDirection.South, 1); | |
} | |
return new Cuboid(this.worldName, face.getLowerX(), this.y1, this.z1, this.x2, this.y2, this.z2); | |
case East: | |
while (face.containsOnly(0) && face.getLowerZ() > this.getLowerZ()) { | |
face = face.shift(CuboidDirection.East, 1); | |
} | |
return new Cuboid(this.worldName, this.x1, this.y1, this.z1, this.x2, this.y2, face.getUpperZ()); | |
case West: | |
while (face.containsOnly(0) && face.getUpperZ() < this.getUpperZ()) { | |
face = face.shift(CuboidDirection.West, 1); | |
} | |
return new Cuboid(this.worldName, this.x1, this.y1, face.getLowerZ(), this.x2, this.y2, this.z2); | |
default: | |
throw new IllegalArgumentException("Invalid direction " + dir); | |
} | |
} | |
/** | |
* Get the Cuboid representing the face of this Cuboid. The resulting Cuboid will be one block thick in the axis perpendicular to the requested face. | |
* | |
* @param dir - which face of the Cuboid to get | |
* @return The Cuboid representing this Cuboid's requested face | |
*/ | |
public Cuboid getFace(CuboidDirection dir) { | |
switch (dir) { | |
case Down: | |
return new Cuboid(this.worldName, this.x1, this.y1, this.z1, this.x2, this.y1, this.z2); | |
case Up: | |
return new Cuboid(this.worldName, this.x1, this.y2, this.z1, this.x2, this.y2, this.z2); | |
case North: | |
return new Cuboid(this.worldName, this.x1, this.y1, this.z1, this.x1, this.y2, this.z2); | |
case South: | |
return new Cuboid(this.worldName, this.x2, this.y1, this.z1, this.x2, this.y2, this.z2); | |
case East: | |
return new Cuboid(this.worldName, this.x1, this.y1, this.z1, this.x2, this.y2, this.z1); | |
case West: | |
return new Cuboid(this.worldName, this.x1, this.y1, this.z2, this.x2, this.y2, this.z2); | |
default: | |
throw new IllegalArgumentException("Invalid direction " + dir); | |
} | |
} | |
/** | |
* Check if the Cuboid contains only blocks of the given type | |
* | |
* @param blockId - The block ID to check for | |
* @return true if this Cuboid contains only blocks of the given type | |
*/ | |
public boolean containsOnly(int blockId) { | |
for (Block b : this) { | |
if (b.getTypeId() != blockId) return false; | |
} | |
return true; | |
} | |
/** | |
* Get the Cuboid big enough to hold both this Cuboid and the given one. | |
* | |
* @param other - The other cuboid. | |
* @return A new Cuboid large enough to hold this Cuboid and the given Cuboid | |
*/ | |
public Cuboid getBoundingCuboid(Cuboid other) { | |
if (other == null) return this; | |
int xMin = Math.min(this.getLowerX(), other.getLowerX()); | |
int yMin = Math.min(this.getLowerY(), other.getLowerY()); | |
int zMin = Math.min(this.getLowerZ(), other.getLowerZ()); | |
int xMax = Math.max(this.getUpperX(), other.getUpperX()); | |
int yMax = Math.max(this.getUpperY(), other.getUpperY()); | |
int zMax = Math.max(this.getUpperZ(), other.getUpperZ()); | |
return new Cuboid(this.worldName, xMin, yMin, zMin, xMax, yMax, zMax); | |
} | |
/** | |
* Get a block relative to the lower NE point of the Cuboid. | |
* | |
* @param x - The X co-ordinate | |
* @param y - The Y co-ordinate | |
* @param z - The Z co-ordinate | |
* @return The block at the given position | |
*/ | |
public Block getRelativeBlock(int x, int y, int z) { | |
return this.getWorld().getBlockAt(this.x1 + x, this.y1 + y, this.z1 + z); | |
} | |
/** | |
* Get a block relative to the lower NE point of the Cuboid in the given World. This | |
* version of getRelativeBlock() should be used if being called many times, to avoid | |
* excessive calls to getWorld(). | |
* | |
* @param w - The world | |
* @param x - The X co-ordinate | |
* @param y - The Y co-ordinate | |
* @param z - The Z co-ordinate | |
* @return The block at the given position | |
*/ | |
public Block getRelativeBlock(World w, int x, int y, int z) { | |
return w.getBlockAt(this.x1 + x, y1 + y, this.z1 + z); | |
} | |
/** | |
* Get a list of the chunks which are fully or partially contained in this cuboid. | |
* | |
* @return A list of Chunk objects | |
*/ | |
public List<Chunk> getChunks() { | |
List<Chunk> res = new ArrayList<Chunk>(); | |
World w = this.getWorld(); | |
int x1 = this.getLowerX() & ~0xf; | |
int x2 = this.getUpperX() & ~0xf; | |
int z1 = this.getLowerZ() & ~0xf; | |
int z2 = this.getUpperZ() & ~0xf; | |
for (int x = x1; x <= x2; x += 16) { | |
for (int z = z1; z <= z2; z += 16) { | |
res.add(w.getChunkAt(x >> 4, z >> 4)); | |
} | |
} | |
return res; | |
} | |
public Iterator<Block> iterator() { | |
return new CuboidIterator(this.getWorld(), this.x1, this.y1, this.z1, this.x2, this.y2, this.z2); | |
} | |
@Override | |
public Cuboid clone() { | |
return new Cuboid(this); | |
} | |
@Override | |
public String toString() { | |
return new String("Cuboid: " + this.worldName + "," + this.x1 + "," + this.y1 + "," + this.z1 + "=>" + this.x2 + "," + this.y2 + "," + this.z2); | |
} | |
public class CuboidIterator implements Iterator<Block> { | |
private World w; | |
private int baseX, baseY, baseZ; | |
private int x, y, z; | |
private int sizeX, sizeY, sizeZ; | |
public CuboidIterator(World w, int x1, int y1, int z1, int x2, int y2, int z2) { | |
this.w = w; | |
this.baseX = x1; | |
this.baseY = y1; | |
this.baseZ = z1; | |
this.sizeX = Math.abs(x2 - x1) + 1; | |
this.sizeY = Math.abs(y2 - y1) + 1; | |
this.sizeZ = Math.abs(z2 - z1) + 1; | |
this.x = this.y = this.z = 0; | |
} | |
public boolean hasNext() { | |
return this.x < this.sizeX && this.y < this.sizeY && this.z < this.sizeZ; | |
} | |
public Block next() { | |
Block b = this.w.getBlockAt(this.baseX + this.x, this.baseY + this.y, this.baseZ + this.z); | |
if (++x >= this.sizeX) { | |
this.x = 0; | |
if (++this.y >= this.sizeY) { | |
this.y = 0; | |
++this.z; | |
} | |
} | |
return b; | |
} | |
public void remove() { | |
} | |
} | |
public enum CuboidDirection { | |
North, East, South, West, Up, Down, Horizontal, Vertical, Both, Unknown; | |
public CuboidDirection opposite() { | |
switch (this) { | |
case North: | |
return South; | |
case East: | |
return West; | |
case South: | |
return North; | |
case West: | |
return East; | |
case Horizontal: | |
return Vertical; | |
case Vertical: | |
return Horizontal; | |
case Up: | |
return Down; | |
case Down: | |
return Up; | |
case Both: | |
return Both; | |
default: | |
return Unknown; | |
} | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment