solonovamax dc46e4596d
Add linting + fix warnings
Signed-off-by: solonovamax <solonovamax@12oclockpoint.com>
2020-11-07 18:27:39 -05:00

425 lines
18 KiB
Java

package com.dfsek.terra.structure;
import com.dfsek.terra.Debug;
import com.dfsek.terra.procgen.math.Vector2;
import org.bukkit.Bukkit;
import org.bukkit.Chunk;
import org.bukkit.Location;
import org.bukkit.Material;
import org.bukkit.block.Block;
import org.bukkit.block.BlockFace;
import org.bukkit.block.BlockState;
import org.bukkit.block.Sign;
import org.bukkit.block.data.BlockData;
import org.bukkit.block.data.Directional;
import org.bukkit.block.data.MultipleFacing;
import org.bukkit.block.data.Orientable;
import org.bukkit.block.data.Rail;
import org.bukkit.block.data.Rotatable;
import org.bukkit.block.data.type.RedstoneWire;
import org.bukkit.inventory.BlockInventoryHolder;
import org.jetbrains.annotations.NotNull;
import org.polydev.gaea.math.Range;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Objects;
import java.util.UUID;
import java.util.function.Consumer;
import static com.dfsek.terra.util.structure.RotationUtil.*;
@SuppressWarnings("unused")
public class Structure implements Serializable {
private static final long serialVersionUID = -6664585217063842035L;
private final StructureContainedBlock[][][] structure;
private final StructureInfo structureInfo;
private final String id;
private final UUID uuid;
private final HashSet<StructureContainedBlock> spawns;
private final HashSet<StructureContainedInventory> inventories;
public Structure(@NotNull Location l1, @NotNull Location l2, @NotNull String id) throws InitializationException {
int centerX = -1, centerZ = -1;
this.id = id;
this.uuid = UUID.randomUUID();
this.spawns = new HashSet<>();
this.inventories = new HashSet<>();
if(l1.getX() > l2.getX() || l1.getY() > l2.getY() || l1.getZ() > l2.getZ())
throw new IllegalArgumentException("Invalid locations provided!");
structure = new StructureContainedBlock[l2.getBlockX() - l1.getBlockX() + 1][l2.getBlockZ() - l1.getBlockZ() + 1][l2.getBlockY() - l1.getBlockY() + 1];
for(int x = 0; x <= l2.getBlockX() - l1.getBlockX(); x++) {
for(int z = 0; z <= l2.getBlockZ() - l1.getBlockZ(); z++) {
for(int y = 0; y <= l2.getBlockY() - l1.getBlockY(); y++) {
Block b = Objects.requireNonNull(l1.getWorld()).getBlockAt(l1.clone().add(x, y, z));
BlockState state = b.getState();
BlockData d = b.getBlockData();
boolean useState = true;
StructureContainedBlock.Pull pull = StructureContainedBlock.Pull.NONE;
int pullOffset = 0;
StructureSpawnRequirement requirement = StructureSpawnRequirement.BLANK;
try {
if(state instanceof Sign) { // Magic sign stuff
Sign s = (Sign) b.getState();
if(s.getLine(0).equals("[TERRA]")) {
try {
d = Bukkit.createBlockData(s.getLine(2) + s.getLine(3));
useState = false;
if(s.getLine(1).equals("[CENTER]")) {
centerX = x;
centerZ = z;
} else if(s.getLine(1).startsWith("[SPAWN=") && s.getLine(1).endsWith("]")) {
String og = s.getLine(1);
String spawn = og.substring(og.indexOf("=") + 1, og.length() - 1);
try {
requirement = StructureSpawnRequirement.valueOf(spawn.toUpperCase());
} catch(IllegalArgumentException e) {
throw new InitializationException("Invalid spawn type: " + spawn, b.getLocation());
}
} else if(s.getLine(1).startsWith("[PULL=") && s.getLine(1).endsWith("]")) {
String og = s.getLine(1);
String spawn = og.substring(og.indexOf("=") + 1, og.indexOf("_"));
pullOffset = Integer.parseInt(og.substring(og.indexOf("_") + 1, og.length() - 1));
try {
pull = StructureContainedBlock.Pull.valueOf(spawn);
} catch(IllegalArgumentException e) {
throw new InitializationException("Invalid pull type: " + spawn, b.getLocation());
}
} else {
throw new InitializationException("Invalid Magic Sign: \"" + s.getLine(1) + "\"", b.getLocation());
}
} catch(IllegalArgumentException e) {
throw new InitializationException("Invalid Block Data on sign: \"" + s.getLine(2) + s.getLine(3) + "\"", b.getLocation());
}
}
}
} catch(StringIndexOutOfBoundsException e) {
throw new InitializationException("Invalid sign.", b.getLocation());
}
StructureContainedBlock block = new StructureContainedBlock(x, y, z, useState ? state : null, d, requirement, pull, pullOffset);
if(state instanceof BlockInventoryHolder) {
inventories.add(new StructureContainedInventory(((BlockInventoryHolder) state).getInventory(), block));
}
if(!requirement.equals(StructureSpawnRequirement.BLANK)) spawns.add(block);
structure[x][z][y] = block;
}
}
}
if(centerX < 0 || centerZ < 0) throw new InitializationException("No structure center specified.", null);
structureInfo = new StructureInfo(l2.getBlockX() - l1.getBlockX() + 1, l2.getBlockY() - l1.getBlockY() + 1, l2.getBlockZ() - l1.getBlockZ() + 1, new Vector2(centerX, centerZ));
}
@NotNull
public static Structure load(@NotNull File f) throws IOException {
try {
return fromFile(f);
} catch(ClassNotFoundException e) {
throw new IllegalArgumentException("Provided file does not contain a GaeaStructure.");
}
}
/**
* Load a structure from a file.
*
* @param f File to load from
* @return The structure loaded
* @throws IOException If file access error occurs
* @throws ClassNotFoundException If structure data is invalid.
*/
@NotNull
private static Structure fromFile(@NotNull File f) throws IOException, ClassNotFoundException {
ObjectInputStream ois = new ObjectInputStream(new FileInputStream(f));
Object o = ois.readObject();
ois.close();
return (Structure) o;
}
@NotNull
public static Structure fromStream(@NotNull InputStream f) throws IOException, ClassNotFoundException {
ObjectInputStream ois = new ObjectInputStream(f);
Object o = ois.readObject();
ois.close();
return (Structure) o;
}
/**
* Paste the structure at a Location, ignoring chunk boundaries.
*
* @param origin Origin location
* @param r Rotation
*/
public void paste(@NotNull Location origin, Rotation r) {
Range xRange = getRange(Axis.X, r);
Range zRange = getRange(Axis.Z, r);
this.executeForBlocksInRange(xRange, getRange(Axis.Y, r), zRange, block -> pasteBlock(block, origin, r), r);
}
/**
* Paste a single StructureDefinedBlock at an origin location, offset by its coordinates.
*
* @param block The block to paste
* @param origin The origin location
* @param r The rotation of the structure
*/
private void pasteBlock(StructureContainedBlock block, Location origin, Rotation r) {
BlockData data = block.getBlockData().clone();
if(!data.getMaterial().equals(Material.STRUCTURE_VOID)) {
Location loc = origin.clone().add(block.getX(), block.getY(), block.getZ());
Block worldBlock = loc.getBlock();
main:
while(worldBlock.isEmpty()) {
if(loc.getBlockY() > 255 || loc.getBlockY() < 0) return;
if(block.getPull() == null) break;
switch(block.getPull()) {
case UP:
worldBlock = worldBlock.getRelative(BlockFace.UP);
break;
case DOWN:
worldBlock = worldBlock.getRelative(BlockFace.DOWN);
break;
default:
break main;
}
}
int offset = block.getPullOffset();
if(offset != 0)
worldBlock = worldBlock.getRelative((offset > 0) ? BlockFace.UP : BlockFace.DOWN, Math.abs(offset));
if(data instanceof Rotatable) {
BlockFace rt = getRotatedFace(((Rotatable) data).getRotation(), r);
((Rotatable) data).setRotation(rt);
} else if(data instanceof Directional) {
BlockFace rt = getRotatedFace(((Directional) data).getFacing(), r);
((Directional) data).setFacing(rt);
} else if(data instanceof MultipleFacing) {
MultipleFacing mfData = (MultipleFacing) data;
Map<BlockFace, Boolean> faces = new HashMap<>();
for(BlockFace f : mfData.getAllowedFaces()) {
faces.put(f, mfData.hasFace(f));
}
for(Map.Entry<BlockFace, Boolean> face : faces.entrySet()) {
mfData.setFace(getRotatedFace(face.getKey(), r), face.getValue());
}
} else if(data instanceof Rail) {
Rail.Shape newShape = getRotatedRail(((Rail) data).getShape(), r);
((Rail) data).setShape(newShape);
} else if(data instanceof Orientable) {
org.bukkit.Axis newAxis = getRotatedAxis(((Orientable) data).getAxis(), r);
((Orientable) data).setAxis(newAxis);
} else if(data instanceof RedstoneWire) {
Map<BlockFace, RedstoneWire.Connection> connections = new HashMap<>();
RedstoneWire rData = (RedstoneWire) data;
for(BlockFace f : rData.getAllowedFaces()) {
connections.put(f, rData.getFace(f));
}
for(Map.Entry<BlockFace, RedstoneWire.Connection> e : connections.entrySet()) {
rData.setFace(getRotatedFace(e.getKey(), r), e.getValue());
}
}
worldBlock.setBlockData(data, false);
if(block.getState() != null) {
block.getState().getState(worldBlock.getState()).update(true, false);
}
}
}
/**
* Execute a Consumer for all blocks in a cuboid region defined by 3 Ranges, accounting for rotation.
*
* @param xM X Range
* @param yM Y Range
* @param zM Z Range
* @param exec Consumer to execute for each block.
* @param r Rotation
*/
private void executeForBlocksInRange(Range xM, Range yM, Range zM, Consumer<StructureContainedBlock> exec, Rotation r) {
for(int x : xM) {
for(int y : yM) {
for(int z : zM) {
Vector2 c = getRotatedCoords(new Vector2(x - structureInfo.getCenterX(), z - structureInfo.getCenterZ()), r);
c.add(new Vector2(structureInfo.getCenterX(), structureInfo.getCenterZ()));
if(isInStructure((int) c.getX(), y, (int) c.getZ())) {
StructureContainedBlock b = structure[(int) c.getX()][(int) c.getZ()][y];
exec.accept(new StructureContainedBlock(x - getStructureInfo().getCenterX(), y, z - getStructureInfo().getCenterZ(), b.getState(), b.getBlockData(), b.getRequirement(), b.getPull(), b.getPullOffset()));
}
}
}
}
}
/**
* Get GaeaStructureInfo object
*
* @return Structure Info
*/
@NotNull
public StructureInfo getStructureInfo() {
return structureInfo;
}
/**
* Test whether a set of coordinates is within the current structure
*
* @param x X coordinate
* @param y Y coordinate
* @param z Z coordinate
* @return True if coordinate set is in structure, false if it is not.
*/
private boolean isInStructure(int x, int y, int z) {
return x < structureInfo.getSizeX() && y < structureInfo.getSizeY() && z < structureInfo.getSizeZ() && x >= 0 && y >= 0 && z >= 0;
}
@NotNull
public Range getRange(@NotNull Axis a, @NotNull Rotation r) {
if(a.equals(Axis.Y)) return getRawRange(a);
Vector2 center = new Vector2(structureInfo.getCenterX(), structureInfo.getCenterZ());
Range x = getRawRange(Axis.X);
Range z = getRawRange(Axis.Z);
Vector2 min = getRotatedCoords(new Vector2(x.getMin(), z.getMin()).subtract(center), r.inverse()).add(center);
Vector2 max = getRotatedCoords(new Vector2(x.getMax(), z.getMax()).subtract(center), r.inverse()).add(center);
if(a.equals(Axis.X))
return new Range((int) Math.floor(Math.min(min.getX(), max.getX())), (int) Math.ceil(Math.max(min.getX(), max.getX())) + 1);
else
return new Range((int) Math.floor(Math.min(min.getZ(), max.getZ())), (int) Math.ceil(Math.max(min.getZ(), max.getZ())) + 1);
}
@NotNull
private Range getRawRange(@NotNull Axis a) {
switch(a) {
case X:
return new Range(0, structureInfo.getSizeX());
case Y:
return new Range(0, structureInfo.getSizeY());
case Z:
return new Range(0, structureInfo.getSizeZ());
default:
throw new IllegalArgumentException();
}
}
public boolean checkSpawns(Location origin, Rotation r) {
for(StructureContainedBlock b : spawns) {
Vector2 rot = getRotatedCoords(new Vector2(b.getX() - structureInfo.getCenterX(), b.getZ() - structureInfo.getCenterZ()), r);
if(!b.getRequirement().matches(origin.getWorld(), (int) rot.getX() + origin.getBlockX(), origin.getBlockY() + b.getY(), (int) rot.getZ() + origin.getBlockZ()))
return false;
}
return true;
}
public HashSet<StructureContainedInventory> getInventories() {
return inventories;
}
/**
* Paste structure at an origin location, confined to a single chunk.
*
* @param origin Origin location
* @param chunk Chunk to confine pasting to
* @param r Rotation
*/
public void paste(Location origin, Chunk chunk, Rotation r) {
int xOr = (chunk.getX() << 4);
int zOr = (chunk.getZ() << 4);
Range intersectX = new Range(xOr, xOr + 16).sub(origin.getBlockX() - structureInfo.getCenterX());
Range intersectZ = new Range(zOr, zOr + 16).sub(origin.getBlockZ() - structureInfo.getCenterZ());
if(intersectX == null || intersectZ == null) return;
executeForBlocksInRange(intersectX, getRange(Axis.Y, r), intersectZ, block -> pasteBlock(block, origin, r), r);
Debug.info(intersectX.toString() + " : " + intersectZ.toString());
}
/**
* From an origin location (First bound) fetch the second bound.
*
* @param origin Origin location
* @return Other bound location
*/
public Location getOtherBound(Location origin) {
return origin.clone().add(structureInfo.getSizeX(), structureInfo.getSizeY(), structureInfo.getSizeZ());
}
/**
* Save the structure to a file
*
* @param f File to save to
* @throws IOException If file access error occurs
*/
public void save(@NotNull File f) throws IOException {
toFile(this, f);
}
private static void toFile(@NotNull Serializable o, @NotNull File f) throws IOException {
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(f));
oos.writeObject(o);
oos.close();
}
@NotNull
public String getId() {
return id;
}
@NotNull
public UUID getUuid() {
return uuid;
}
public enum Axis {
X, Y, Z
}
public enum Rotation {
CW_90(90), CW_180(180), CCW_90(270), NONE(0);
private final int degrees;
Rotation(int degrees) {
this.degrees = degrees;
}
public static Rotation fromDegrees(int deg) {
switch(Math.floorMod(deg, 360)) {
case 0:
return Rotation.NONE;
case 90:
return Rotation.CW_90;
case 180:
return Rotation.CW_180;
case 270:
return Rotation.CCW_90;
default:
throw new IllegalArgumentException();
}
}
public int getDegrees() {
return degrees;
}
public Rotation inverse() {
switch(this) {
case NONE:
return NONE;
case CCW_90:
return CW_90;
case CW_90:
return CCW_90;
case CW_180:
return CW_180;
default:
throw new IllegalArgumentException();
}
}
}
}