/*
* To change this template, choose Tools | Templates
* and open the template in the editor.
*/
package org.pepsoft.worldpainter.layers.bo2;
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 javax.vecmath.Point3i;
import java.io.*;
import java.nio.charset.Charset;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
*
* @author pepijn
*/
public final class Bo2Object extends AbstractObject implements Bo2ObjectProvider {
private Bo2Object(String name, Map<String, String> properties, Map<Point3i, Bo2BlockSpec> 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);
}
if (properties.containsKey(KEY_NEEDS_FOUNDATION) && ! Boolean.valueOf(properties.get(KEY_NEEDS_FOUNDATION))) {
if (attributes == null) attributes = new HashMap<>();
attributes.put(ATTRIBUTE_NEEDS_FOUNDATION.key, false);
}
if (properties.containsKey(KEY_SPAWN_LAVA) && Boolean.valueOf(properties.get(KEY_SPAWN_LAVA))) {
if (attributes == null) attributes = new HashMap<>();
attributes.put(ATTRIBUTE_SPAWN_IN_LAVA.key, true);
}
if (properties.containsKey(KEY_SPAWN_WATER) && Boolean.valueOf(properties.get(KEY_SPAWN_WATER))) {
if (attributes == null) attributes = new HashMap<>();
attributes.put(ATTRIBUTE_SPAWN_IN_WATER.key, true);
}
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 Bo2Object getObject() {
return this;
}
@Override
public List<Entity> getEntities() {
return null;
}
@Override
public List<TileEntity> getTileEntities() {
return null;
}
@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 Bo2Object clone() {
Bo2Object clone = (Bo2Object) 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 bo2 format from a file.
*
* @param file The file from which to load the object.
* @return A new <code>Bo2Object</code> containing the contents of the
* specified file.
* @throws IOException If an I/O error occurred while reading the file.
*/
public static Bo2Object 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);
}
private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException {
in.defaultReadObject();
// Legacy
if (version == 0) {
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);
}
if (properties.containsKey(KEY_NEEDS_FOUNDATION) && ! Boolean.valueOf(properties.get(KEY_NEEDS_FOUNDATION))) {
if (attributes == null) attributes = new HashMap<>();
attributes.put(ATTRIBUTE_NEEDS_FOUNDATION.key, false);
}
if (properties.containsKey(KEY_SPAWN_LAVA) && Boolean.valueOf(properties.get(KEY_SPAWN_LAVA))) {
if (attributes == null) attributes = new HashMap<>();
attributes.put(ATTRIBUTE_SPAWN_IN_LAVA.key, true);
}
if (properties.containsKey(KEY_SPAWN_WATER) && Boolean.valueOf(properties.get(KEY_SPAWN_WATER))) {
if (attributes == null) attributes = new HashMap<>();
attributes.put(ATTRIBUTE_SPAWN_IN_WATER.key, true);
}
version = 1;
}
if (version == 1) {
if (! attributes.containsKey(ATTRIBUTE_LEAF_DECAY_MODE.key)) {
attributes.put(ATTRIBUTE_LEAF_DECAY_MODE.key, LEAF_DECAY_ON);
}
version = 2;
}
}
/**
* Load a custom object in bo2 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>Bo2Object</code> containing the contents of the
* specified file.
* @throws IOException If an I/O error occurred while reading the file.
*/
public static Bo2Object load(String objectName, File file) throws IOException {
Bo2Object object = load(objectName, new FileInputStream(file));
object.setAttribute(ATTRIBUTE_FILE, file);
return object;
}
/**
* Load a custom object in bo2 format from an input stream. The stream is
* closed before exiting this method.
*
* @param objectName The name of the object.
* @param stream The input stream from which to load the object.
* @return A new <code>Bo2Object</code> containing the contents of the
* specified stream.
* @throws IOException If an I/O error occurred while reading the stream.
*/
public static Bo2Object load(String objectName, InputStream stream) throws IOException {
try (BufferedReader in = new BufferedReader(new InputStreamReader(stream, Charset.forName("US-ASCII")))) {
Map<String, String> properties = new HashMap<>();
Map<Point3i, Bo2BlockSpec> blocks = new HashMap<>();
boolean readingMetaData = false, readingData = false;
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) {
if (line.trim().length() == 0) {
continue;
}
if (readingMetaData) {
if (line.equals("[DATA]")) {
readingMetaData = false;
readingData = true;
} else {
int p = line.indexOf('=');
String name = line.substring(0, p).trim();
String value = line.substring(p + 1).trim();
properties.put(name, value);
}
} else if (readingData) {
int p = line.indexOf(':');
String coordinates = line.substring(0, p);
String spec = line.substring(p + 1);
p = coordinates.indexOf(',');
int x = Integer.parseInt(coordinates.substring(0, p));
int p2 = coordinates.indexOf(',', p + 1);
int y = Integer.parseInt(coordinates.substring(p + 1, p2));
int z = Integer.parseInt(coordinates.substring(p2 + 1));
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;
}
p = spec.indexOf('.');
int blockId, data = 0;
int[] branch = null;
if (p == -1) {
blockId = Integer.parseInt(spec);
} else {
blockId = Integer.parseInt(spec.substring(0, p));
p2 = spec.indexOf('#', p + 1);
if (p2 == -1) {
data = Integer.parseInt(spec.substring(p + 1));
} else {
data = Integer.parseInt(spec.substring(p + 1, p2));
p = spec.indexOf('@', p2 + 1);
branch = new int[]{Integer.parseInt(spec.substring(p2 + 1, p)), Integer.parseInt(spec.substring(p + 1))};
}
}
Point3i coords = new Point3i(x, y, z);
blocks.put(coords, new Bo2BlockSpec(coords, Material.get(blockId, data), branch));
} else {
if (line.equals("[META]")) {
readingMetaData = true;
}
}
}
if (blocks.isEmpty()) {
throw new IOException("No blocks found in the file; is this a bo2 object?");
}
return new Bo2Object(objectName, properties, blocks, new Point3i(-lowestX, -lowestY, -lowestZ), new Point3i(highestX - lowestX + 1, highestY - lowestY + 1, highestZ - lowestZ + 1), null);
}
}
private String name;
private final Map<String, String> properties;
private final Map<Point3i, Bo2BlockSpec> blocks;
private Point3i origin, dimensions;
private Map<String, Serializable> attributes;
private int version = 2;
public static final String KEY_SPAWN_WATER = "spawnWater";
public static final String KEY_SPAWN_LAVA = "spawnLava";
public static final String KEY_NEEDS_FOUNDATION = "needsFoundation";
public static final String KEY_RANDOM_ROTATION = "randomRotation";
private static final long serialVersionUID = 1L;
}