NBTExplorer/SubstrateCS/Source/Data/Map.cs
2011-11-09 21:46:19 -05:00

342 lines
11 KiB
C#

using System;
using System.Collections.Generic;
using System.Text;
using Substrate.Core;
using Substrate.Nbt;
using System.IO;
namespace Substrate.Data
{
/// <summary>
/// Represents the complete data of a Map item.
/// </summary>
public class Map : INbtObject<Map>, ICopyable<Map>
{
private static SchemaNodeCompound _schema = new SchemaNodeCompound()
{
new SchemaNodeCompound("data")
{
new SchemaNodeScaler("scale", TagType.TAG_BYTE),
new SchemaNodeScaler("dimension", TagType.TAG_BYTE),
new SchemaNodeScaler("height", TagType.TAG_SHORT),
new SchemaNodeScaler("width", TagType.TAG_SHORT),
new SchemaNodeScaler("xCenter", TagType.TAG_INT),
new SchemaNodeScaler("zCenter", TagType.TAG_INT),
new SchemaNodeArray("colors"),
},
};
private TagNodeCompound _source;
private NbtWorld _world;
private int _id;
private byte _scale;
private byte _dimension;
private short _height;
private short _width;
private int _x;
private int _z;
private byte[] _colors;
/// <summary>
/// Creates a new default <see cref="Map"/> object.
/// </summary>
public Map ()
{
_scale = 3;
_dimension = 0;
_height = 128;
_width = 128;
_colors = new byte[_width * _height];
}
/// <summary>
/// Creates a new <see cref="Map"/> object with copied data.
/// </summary>
/// <param name="p">A <see cref="Map"/> to copy data from.</param>
protected Map (Map p)
{
_world = p._world;
_id = p._id;
_scale = p._scale;
_dimension = p._dimension;
_height = p._height;
_width = p._width;
_x = p._x;
_z = p._z;
_colors = new byte[_width * _height];
if (p._colors != null) {
p._colors.CopyTo(_colors, 0);
}
}
/// <summary>
/// Gets or sets the id value associated with this map.
/// </summary>
public int Id
{
get { return _id; }
set
{
if (_id < 0 || _id >= 65536) {
throw new ArgumentOutOfRangeException("value", value, "Map Ids must be in the range [0, 65535].");
}
_id = value;
}
}
/// <summary>
/// Gets or sets the scale of the map. Acceptable values are 0 (1:1) to 4 (1:16).
/// </summary>
public int Scale
{
get { return _scale; }
set { _scale = (byte)value; }
}
/// <summary>
/// Gets or sets the (World) Dimension of the map.
/// </summary>
public int Dimension
{
get { return _dimension; }
set { _dimension = (byte)value; }
}
/// <summary>
/// Gets or sets the height of the map.
/// </summary>
/// <remarks>If the new height dimension is different, the map's color data will be reset.</remarks>
/// <exception cref="ArgumentOutOfRangeException">Thrown if the new height value is zero or negative.</exception>
public int Height
{
get { return _height; }
set
{
if (value <= 0) {
throw new ArgumentOutOfRangeException("value", "Height must be a positive number");
}
if (_height != value) {
_height = (short)value;
_colors = new byte[_width * _height];
}
}
}
/// <summary>
/// Gets or sets the width of the map.
/// </summary>
/// <remarks>If the new width dimension is different, the map's color data will be reset.</remarks>
/// <exception cref="ArgumentOutOfRangeException">Thrown if the new width value is zero or negative.</exception>
public int Width
{
get { return _width; }
set
{
if (value <= 0) {
throw new ArgumentOutOfRangeException("value", "Width must be a positive number");
}
if (_width != value) {
_width = (short)value;
_colors = new byte[_width * _height];
}
}
}
/// <summary>
/// Gets or sets the global X-coordinate that this map is centered on, in blocks.
/// </summary>
public int X
{
get { return _x; }
set { _x = value; }
}
/// <summary>
/// Gets or sets the global Z-coordinate that this map is centered on, in blocks.
/// </summary>
public int Z
{
get { return _z; }
set { _z = value; }
}
/// <summary>
/// Gets the raw byte array of the map's color index values.
/// </summary>
public byte[] Colors
{
get { return _colors; }
}
/// <summary>
/// Gets or sets a color index value within the map's internal colors bitmap.
/// </summary>
/// <param name="x">The X-coordinate to get or set.</param>
/// <param name="z">The Z-coordinate to get or set.</param>
/// <exception cref="IndexOutOfRangeException">Thrown when the X- or Z-coordinates exceed the map dimensions.</exception>
public byte this[int x, int z]
{
get
{
if (x < 0 || x >= _width || z < 0 || z >= _height) {
throw new IndexOutOfRangeException();
}
return _colors[x + _width * z];
}
set
{
if (x < 0 || x >= _width || z < 0 || z >= _height) {
throw new IndexOutOfRangeException();
}
_colors[x + _width * z] = value;
}
}
/// <summary>
/// Saves a <see cref="Map"/> object to disk as a standard compressed NBT stream.
/// </summary>
/// <returns>True if the map was saved; false otherwise.</returns>
/// <exception cref="Exception">Thrown when an error is encountered writing out the level.</exception>
public bool Save ()
{
if (_world == null) {
return false;
}
try {
string path = Path.Combine(_world.Path, _world.DataDirectory);
NBTFile nf = new NBTFile(Path.Combine(path, "map_" + _id + ".dat"));
Stream zipstr = nf.GetDataOutputStream();
if (zipstr == null) {
NbtIOException nex = new NbtIOException("Failed to initialize compressed NBT stream for output");
nex.Data["Map"] = this;
throw nex;
}
new NbtTree(BuildTree() as TagNodeCompound).WriteTo(zipstr);
zipstr.Close();
return true;
}
catch (Exception ex) {
Exception mex = new Exception("Could not save map file.", ex); // TODO: Exception Type
mex.Data["Map"] = this;
throw mex;
}
}
#region INBTObject<Map> Members
/// <summary>
/// Attempt to load a Map subtree into the <see cref="Map"/> without validation.
/// </summary>
/// <param name="tree">The root node of a Map subtree.</param>
/// <returns>The <see cref="Map"/> returns itself on success, or null if the tree was unparsable.</returns>
public virtual Map LoadTree (TagNode tree)
{
TagNodeCompound dtree = tree as TagNodeCompound;
if (dtree == null) {
return null;
}
TagNodeCompound ctree = dtree["data"].ToTagCompound();
_scale = ctree["scale"].ToTagByte();
_dimension = ctree["dimension"].ToTagByte();
_height = ctree["height"].ToTagShort();
_width = ctree["width"].ToTagShort();
_x = ctree["xCenter"].ToTagInt();
_z = ctree["zCenter"].ToTagInt();
_colors = ctree["colors"].ToTagByteArray();
_source = ctree.Copy() as TagNodeCompound;
return this;
}
/// <summary>
/// Attempt to load a Map subtree into the <see cref="Map"/> with validation.
/// </summary>
/// <param name="tree">The root node of a Map subtree.</param>
/// <returns>The <see cref="Map"/> returns itself on success, or null if the tree failed validation.</returns>
public virtual Map LoadTreeSafe (TagNode tree)
{
if (!ValidateTree(tree)) {
return null;
}
Map map = LoadTree(tree);
if (map != null) {
if (map._colors.Length != map._width * map._height) {
throw new Exception("Unexpected length of colors byte array in Map"); // TODO: Expception Type
}
}
return map;
}
/// <summary>
/// Builds a Map subtree from the current data.
/// </summary>
/// <returns>The root node of a Map subtree representing the current data.</returns>
public virtual TagNode BuildTree ()
{
TagNodeCompound data = new TagNodeCompound();
data["scale"] = new TagNodeByte(_scale);
data["dimension"] = new TagNodeByte(_dimension);
data["height"] = new TagNodeShort(_height);
data["width"] = new TagNodeShort(_width);
data["xCenter"] = new TagNodeInt(_x);
data["zCenter"] = new TagNodeInt(_z);
data["colors"] = new TagNodeByteArray(_colors);
if (_source != null) {
data.MergeFrom(_source);
}
TagNodeCompound tree = new TagNodeCompound();
tree.Add("data", data);
return tree;
}
/// <summary>
/// Validate a Map subtree against a schema defintion.
/// </summary>
/// <param name="tree">The root node of a Map subtree.</param>
/// <returns>Status indicating whether the tree was valid against the internal schema.</returns>
public virtual bool ValidateTree (TagNode tree)
{
return new NbtVerifier(tree, _schema).Verify();
}
#endregion
#region ICopyable<Map> Members
/// <summary>
/// Creates a deep-copy of the <see cref="Map"/>.
/// </summary>
/// <returns>A deep-copy of the <see cref="Map"/>.</returns>
public virtual Map Copy ()
{
return new Map(this);
}
#endregion
}
}