/*
* To change this template, choose Tools | Templates
* and open the template in the editor.
*/
package org.pepsoft.worldpainter.layers.bo2;
import com.khorn.terraincontrol.util.minecraftTypes.DefaultMaterial;
import org.jnbt.CompoundTag;
import org.jnbt.NBTInputStream;
import org.jnbt.Tag;
import org.pepsoft.minecraft.Entity;
import org.pepsoft.minecraft.Material;
import org.pepsoft.minecraft.TileEntity;
import org.pepsoft.worldpainter.objects.AbstractObject;
import org.pepsoft.worldpainter.objects.WPObject;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.vecmath.Point3i;
import java.io.*;
import java.nio.charset.Charset;
import java.util.*;
import java.util.stream.Collectors;
import java.util.zip.GZIPInputStream;
/**
*
* @author pepijn
*/
public final class Bo3Object extends AbstractObject implements Bo2ObjectProvider {
private Bo3Object(String name, Map<String, String> properties, Map<Point3i, Bo3BlockSpec> blocks, Point3i origin, Point3i dimensions, Map<String, Serializable> attributes) {
this.name = name;
this.properties = properties;
this.blocks = blocks;
this.origin = origin;
this.dimensions = dimensions;
if ((origin.x != 0) || (origin.y != 0) || (origin.z != 0)) {
if (attributes == null) attributes = new HashMap<>();
attributes.put(ATTRIBUTE_OFFSET.key, new Point3i(-origin.x, -origin.y, -origin.z));
}
if ((! properties.containsKey(KEY_RANDOM_ROTATION)) || (! Boolean.valueOf(properties.get(KEY_RANDOM_ROTATION)))) {
if (attributes == null) attributes = new HashMap<>();
attributes.put(ATTRIBUTE_RANDOM_ROTATION.key, false);
}
this.attributes = attributes;
}
@Override
public String getName() {
return name;
}
@Override
public void setName(String name) {
this.name = name;
}
@Override
public Point3i getDimensions() {
return dimensions;
}
@Override
public Material getMaterial(int x, int y, int z) {
return blocks.get(new Point3i(x - origin.x, y - origin.y, z - origin.z)).getMaterial();
}
@Override
public boolean getMask(int x, int y, int z) {
return blocks.containsKey(new Point3i(x - origin.x, y - origin.y, z - origin.z));
}
@Override
public Bo3Object getObject() {
return this;
}
@Override
public List<Entity> getEntities() {
return null;
}
@Override
public List<TileEntity> getTileEntities() {
if (tileEntities == null) {
tileEntities = blocks.values().stream()
.flatMap(block -> block.getTileEntities().stream())
.map(tileEntity -> {
tileEntity.setX(tileEntity.getX() + origin.x);
tileEntity.setY(tileEntity.getY() + origin.z);
tileEntity.setZ(tileEntity.getZ() + origin.y);
return tileEntity;
})
.collect(Collectors.toList());
}
if (tileEntities.isEmpty()) {
return null;
} else {
return tileEntities;
}
}
@Override
public List<WPObject> getAllObjects() {
return Collections.singletonList(this);
}
@Override
public Map<String, Serializable> getAttributes() {
return attributes;
}
@Override
public void setAttributes(Map<String, Serializable> attributes) {
this.attributes = attributes;
}
@Override
public <T extends Serializable> void setAttribute(AttributeKey<T> key, T value) {
if (value != null) {
if (attributes == null) {
attributes = new HashMap<>();
}
attributes.put(key.key, value);
} else if (attributes != null) {
attributes.remove(key.key);
if (attributes.isEmpty()) {
attributes = null;
}
}
}
@Override
public void setSeed(long seed) {
// Do nothing
}
@Override
public Bo3Object clone() {
Bo3Object clone = (Bo3Object) super.clone();
clone.origin = (Point3i) origin.clone();
clone.dimensions = (Point3i) dimensions.clone();
if (attributes != null) {
clone.attributes = new HashMap<>(attributes);
}
return clone;
}
/**
* Load a custom object in bo3 format from a file. The name of the object
* will be the name of the file, minus the extension.
*
* @param file The file from which to load the object.
* @return A new <code>Bo3Object</code> containing the contents of the
* specified file.
* @throws IOException If an I/O error occurred while reading the file.
*/
public static Bo3Object load(File file) throws IOException {
String name = file.getName();
int p = name.lastIndexOf('.');
if (p != -1) {
name = name.substring(0, p);
}
return load(name, file);
}
/**
* Load a custom object in bo3 format from a file.
*
* @param objectName The name of the object.
* @param file The file from which to load the object.
* @return A new <code>Bo3Object</code> containing the contents of the
* specified file.
* @throws IOException If an I/O error occurred while reading the file.
*/
public static Bo3Object load(String objectName, File file) throws IOException {
try (BufferedReader in = new BufferedReader(new InputStreamReader(new FileInputStream(file), Charset.forName("US-ASCII")))) {
Map<String, String> properties = new HashMap<>();
Map<Point3i, Bo3BlockSpec> blocks = new HashMap<>();
String line;
int lowestX = Integer.MAX_VALUE, highestX = Integer.MIN_VALUE;
int lowestY = Integer.MAX_VALUE, highestY = Integer.MIN_VALUE;
int lowestZ = Integer.MAX_VALUE, highestZ = Integer.MIN_VALUE;
while ((line = in.readLine()) != null) {
line = line.trim();
if (line.isEmpty() || line.startsWith("#")) {
continue;
} else if (line.startsWith("Block")) {
// Block spec
Deque<String> args = getArgs(line);
int x = Integer.parseInt(args.pop());
int z = Integer.parseInt(args.pop());
int y = Integer.parseInt(args.pop());
if (x < lowestX) {lowestX = x;}
if (x > highestX) {highestX = x;}
if (y < lowestY) {lowestY = y;}
if (y > highestY) {highestY = y;}
if (z < lowestZ) {lowestZ = z;}
if (z > highestZ) {highestZ = z;}
Material material = decodeMaterial(args.pop());
TileEntity tileEntity = null;
if (! args.isEmpty()) {
tileEntity = loadTileEntity(file, args.pop());
}
Point3i coords = new Point3i(x, y, z);
blocks.put(coords, new Bo3BlockSpec(coords, material, tileEntity));
} else if (line.startsWith("RandomBlock")) {
// Random block spec
Deque<String> args = getArgs(line);
int x = Integer.parseInt(args.pop());
int z = Integer.parseInt(args.pop());
int y = Integer.parseInt(args.pop());
if (x < lowestX) {lowestX = x;}
if (x > highestX) {highestX = x;}
if (y < lowestY) {lowestY = y;}
if (y > highestY) {highestY = y;}
if (z < lowestZ) {lowestZ = z;}
if (z > highestZ) {highestZ = z;}
List<Bo3BlockSpec.RandomBlock> randomBlocks = new ArrayList<>();
do {
Material material = decodeMaterial(args.pop());
String nbtOrChance = args.pop();
TileEntity tileEntity = null;
int chance;
try {
chance = Integer.parseInt(nbtOrChance);
} catch (NumberFormatException e) {
tileEntity = loadTileEntity(file, nbtOrChance);
chance = Integer.parseInt(args.pop());
}
randomBlocks.add(new Bo3BlockSpec.RandomBlock(material, tileEntity, chance));
} while (! args.isEmpty());
Point3i coords = new Point3i(x, y, z);
blocks.put(coords, new Bo3BlockSpec(coords, randomBlocks.toArray(new Bo3BlockSpec.RandomBlock[randomBlocks.size()])));
} else if (line.startsWith("BlockCheck") || line.startsWith("LightCheck") || line.startsWith("Branch") || line.startsWith("WeightedBranch")) {
logger.warn("Ignoring unsupported bo3 feature " + line);
} else {
int p = line.indexOf(':');
if (p != -1) {
properties.put(line.substring(0, p).trim(), line.substring(p + 1).trim());
} else {
logger.warn("Ignoring unrecognised line: " + line);
}
}
}
if (blocks.isEmpty()) {
throw new IOException("No blocks found in the file; is this a bo3 object?");
}
Map<String, Serializable> attributes = new HashMap<>(Collections.singletonMap(ATTRIBUTE_FILE.key, file));
return new Bo3Object(objectName, properties, blocks, new Point3i(-lowestX, -lowestY, -lowestZ), new Point3i(highestX - lowestX + 1, highestY - lowestY + 1, highestZ - lowestZ + 1), attributes);
}
}
private static Deque<String> getArgs(String line) {
int start = line.indexOf('(');
if (start != -1) {
int end = line.lastIndexOf(')');
if (end != -1) {
Deque<String> args = new ArrayDeque<>();
args.addAll(Arrays.asList(line.substring(start + 1, end).split(",")));
return args;
}
}
throw new IllegalArgumentException("Could not parse line \"" + line + "\"");
}
private static Material decodeMaterial(String materialSpec) {
int p = materialSpec.indexOf(':');
if (p == -1) {
if (Character.isDigit(materialSpec.charAt(0))) {
return Material.get(Integer.parseInt(materialSpec));
} else {
return Material.get(DefaultMaterial.valueOf(materialSpec).id);
}
} else {
String idSpec = materialSpec.substring(0, p).trim();
String dataSpec = materialSpec.substring(p + 1).trim();
if (Character.isDigit(idSpec.charAt(0))) {
return Material.get(Integer.parseInt(idSpec), Integer.parseInt(dataSpec));
} else {
return Material.get(DefaultMaterial.valueOf(idSpec).id, Integer.parseInt(dataSpec));
}
}
}
private static TileEntity loadTileEntity(File bo3File, String nbtFileName) throws IOException {
File nbtFile = new File(bo3File.getParentFile(), nbtFileName);
try (NBTInputStream in = new NBTInputStream(new GZIPInputStream(new FileInputStream(nbtFile)))) {
CompoundTag tag = (CompoundTag) in.readTag();
Map<String, Tag> map = tag.getValue();
if ((map.size() == 1) && (map.values().iterator().next() instanceof CompoundTag)) {
// If the root tag is a CompoundTag which only contains another
// CompoundTag, assume that the nested CompoundTag actually
// contains the data
return TileEntity.fromNBT((CompoundTag) tag.getValue().values().iterator().next());
} else {
return TileEntity.fromNBT(tag);
}
}
}
private String name;
private final Map<String, String> properties;
private final Map<Point3i, Bo3BlockSpec> blocks;
private Point3i origin, dimensions;
private Map<String, Serializable> attributes;
private int version = 1;
private transient List<TileEntity> tileEntities;
public static final String KEY_RANDOM_ROTATION = "RotateRandomly";
private static final Logger logger = LoggerFactory.getLogger(Bo3Object.class);
private static final long serialVersionUID = 1L;
}